diff --git a/InventoryTraker.Web.Tests/App_Start/AutoMapperConfig.cs b/InventoryTraker.Web.Tests/App_Start/AutoMapperConfig.cs new file mode 100644 index 0000000..c095557 --- /dev/null +++ b/InventoryTraker.Web.Tests/App_Start/AutoMapperConfig.cs @@ -0,0 +1,19 @@ +using Heroic.AutoMapper; + +[assembly: WebActivatorEx.PreApplicationStartMethod(typeof(InventoryTraker.Web.Tests.AutoMapperConfig), "Configure")] +namespace InventoryTraker.Web.Tests +{ + public static class AutoMapperConfig + { + public static void Configure() + { + //NOTE: By default, the current project and all referenced projects will be scanned. + // You can customize this by passing in a lambda to filter the assemblies by name, + // like so: + //HeroicAutoMapperConfigurator.LoadMapsFromCallerAndReferencedAssemblies(x => x.Name.StartsWith("YourPrefix")); + HeroicAutoMapperConfigurator.LoadMapsFromCallerAndReferencedAssemblies(); + //If you run into issues with the maps not being located at runtime, try using this method instead: + //HeroicAutoMapperConfigurator.LoadMapsFromAssemblyContainingTypeAndReferencedAssemblies(); + } + } +} \ No newline at end of file diff --git a/InventoryTraker.Web.Tests/InventoryTraker.Web.Tests.csproj b/InventoryTraker.Web.Tests/InventoryTraker.Web.Tests.csproj new file mode 100644 index 0000000..c1fa6d1 --- /dev/null +++ b/InventoryTraker.Web.Tests/InventoryTraker.Web.Tests.csproj @@ -0,0 +1,89 @@ + + + + + Debug + AnyCPU + {03B3BDF2-B2BE-42C1-8D6F-2B4E106A1D4B} + Library + Properties + InventoryTraker.Web.Tests + InventoryTraker.Web.Tests + v4.5.2 + 512 + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + ..\packages\AutoMapper.4.2.0\lib\net45\AutoMapper.dll + True + + + ..\packages\Heroic.AutoMapper.2.0.0\lib\net45\Heroic.AutoMapper.dll + True + + + ..\packages\Microsoft.Web.Infrastructure.1.0.0.0\lib\net40\Microsoft.Web.Infrastructure.dll + True + + + ..\packages\NUnit.3.4.1\lib\net45\nunit.framework.dll + True + + + + + + + + + + + ..\packages\WebActivatorEx.2.1.0\lib\net40\WebActivatorEx.dll + True + + + + + + + + + + + {5e5867a4-6152-4655-a04b-1737df493a41} + InventoryTraker.Web + + + + + + + PreserveNewest + + + + + \ No newline at end of file diff --git a/InventoryTraker.Web.Tests/InventoryTraker.Web.Tests.csproj.DotSettings b/InventoryTraker.Web.Tests/InventoryTraker.Web.Tests.csproj.DotSettings new file mode 100644 index 0000000..306da5b --- /dev/null +++ b/InventoryTraker.Web.Tests/InventoryTraker.Web.Tests.csproj.DotSettings @@ -0,0 +1,2 @@ + + True \ No newline at end of file diff --git a/InventoryTraker.Web.Tests/Models/InventoryAddForm.cs b/InventoryTraker.Web.Tests/Models/InventoryAddForm.cs new file mode 100644 index 0000000..2d1bb1e --- /dev/null +++ b/InventoryTraker.Web.Tests/Models/InventoryAddForm.cs @@ -0,0 +1,40 @@ +using System; +using AutoMapper; +using Heroic.AutoMapper; +using InventoryTraker.Web.Controllers; +using InventoryTraker.Web.Core; +using InventoryTraker.Web.Models; +using NUnit.Framework; + +namespace InventoryTraker.Web.Tests.Models +{ + [TestFixture] + public class InventoryAddFormTests + { + [OneTimeSetUp] + public void StartUp() + { + HeroicAutoMapperConfigurator.LoadMapsFromAssemblyContainingTypeAndReferencedAssemblies(); + } + + [Test] + public void Convert() + { + var form = new InventoryAddForm + { + AddedDate = DateTime.Today, + ExpirationDate = DateTime.Today.AddDays(3), + InventoryTypeId = "1", + Memo = "My Memo", + Quantity = 32 + }; + + var inventory = Mapper.Map(form); + + Assert.That(inventory.AddedDate, Is.EqualTo(form.AddedDate)); + Assert.That(inventory.ExpirationDate, Is.EqualTo(form.ExpirationDate)); + Assert.That(inventory.Memo, Is.EqualTo(form.Memo)); + Assert.That(inventory.Quantity, Is.EqualTo(form.Quantity)); + } + } +} \ No newline at end of file diff --git a/InventoryTraker.Web.Tests/Properties/AssemblyInfo.cs b/InventoryTraker.Web.Tests/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..9b7fdb8 --- /dev/null +++ b/InventoryTraker.Web.Tests/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("InventoryTraker.Web.Tests")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("InventoryTraker.Web.Tests")] +[assembly: AssemblyCopyright("Copyright © 2016")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("03b3bdf2-b2be-42c1-8d6f-2b4e106a1d4b")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/InventoryTraker.Web.Tests/Utilities/Documents/InventoryTypeData.xlsx b/InventoryTraker.Web.Tests/Utilities/Documents/InventoryTypeData.xlsx new file mode 100644 index 0000000..e487126 Binary files /dev/null and b/InventoryTraker.Web.Tests/Utilities/Documents/InventoryTypeData.xlsx differ diff --git a/InventoryTraker.Web.Tests/Utilities/InventoryTypeParserTests.cs b/InventoryTraker.Web.Tests/Utilities/InventoryTypeParserTests.cs new file mode 100644 index 0000000..fd5df0a --- /dev/null +++ b/InventoryTraker.Web.Tests/Utilities/InventoryTypeParserTests.cs @@ -0,0 +1,29 @@ +using System; +using System.IO; +using InventoryTraker.Web.Utilities; +using NUnit.Framework; + +namespace InventoryTraker.Web.Tests.Utilities +{ + [TestFixture] + public class InventoryTypeParserTests + { + private readonly string _documentFolder = + AppDomain.CurrentDomain.BaseDirectory + @"\Utilities\Documents\"; + + [Test] + public void Parse() + { + var fileInfo = new FileInfo(Path.Combine(_documentFolder, "InventoryTypeData.xlsx")); + + var parser = new InventoryTypeParser(fileInfo); + + var inventoryTypes = parser.Parse(); + + foreach (var inventoryType in inventoryTypes) + { + Console.WriteLine($"{inventoryType.Identifier} {inventoryType.Name} {inventoryType.PricePerCase}"); + } + } + } +} diff --git a/InventoryTraker.Web.Tests/app.config b/InventoryTraker.Web.Tests/app.config new file mode 100644 index 0000000..8a403d9 --- /dev/null +++ b/InventoryTraker.Web.Tests/app.config @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/InventoryTraker.Web.Tests/packages.config b/InventoryTraker.Web.Tests/packages.config new file mode 100644 index 0000000..fadfbb0 --- /dev/null +++ b/InventoryTraker.Web.Tests/packages.config @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/InventoryTraker.Web/App_Start/AutoMapperConfig.cs b/InventoryTraker.Web/App_Start/AutoMapperConfig.cs index 9fe2a4b..b8f8f0d 100644 --- a/InventoryTraker.Web/App_Start/AutoMapperConfig.cs +++ b/InventoryTraker.Web/App_Start/AutoMapperConfig.cs @@ -1,5 +1,6 @@ using Heroic.AutoMapper; using InventoryTraker.Web; +using InventoryTraker.Web.Controllers; [assembly: WebActivatorEx.PreApplicationStartMethod(typeof(AutoMapperConfig), "Configure")] namespace InventoryTraker.Web @@ -12,9 +13,9 @@ namespace InventoryTraker.Web // You can customize this by passing in a lambda to filter the assemblies by name, // like so: //HeroicAutoMapperConfigurator.LoadMapsFromCallerAndReferencedAssemblies(x => x.Name.StartsWith("YourPrefix")); - HeroicAutoMapperConfigurator.LoadMapsFromCallerAndReferencedAssemblies(); + //HeroicAutoMapperConfigurator.LoadMapsFromCallerAndReferencedAssemblies(); //If you run into issues with the maps not being located at runtime, try using this method instead: - //HeroicAutoMapperConfigurator.LoadMapsFromAssemblyContainingTypeAndReferencedAssemblies(); + HeroicAutoMapperConfigurator.LoadMapsFromAssemblyContainingTypeAndReferencedAssemblies(); } } } \ No newline at end of file diff --git a/InventoryTraker.Web/App_Start/BundleConfig.cs b/InventoryTraker.Web/App_Start/BundleConfig.cs index 5daf02f..0b8e3de 100644 --- a/InventoryTraker.Web/App_Start/BundleConfig.cs +++ b/InventoryTraker.Web/App_Start/BundleConfig.cs @@ -6,7 +6,7 @@ namespace InventoryTraker.Web { public static void RegisterBundles(BundleCollection bundles) { - bundles.Add(new StyleBundle("~/Content/all.css") + bundles.Add(new StyleBundle("~/Content/all-styles") .Include("~/Content/font-awesome.css") .Include("~/Content/bootstrap.css") .Include("~/Content/sb-admin.css") @@ -14,7 +14,7 @@ namespace InventoryTraker.Web .Include("~/Content/ui-grid.css") ); - bundles.Add(new ScriptBundle("~/js/all.js") + bundles.Add(new ScriptBundle("~/js/all-javascript") .Include("~/Scripts/jquery-1.9.1.js") .Include("~/Scripts/bootstrap.js") .Include("~/Scripts/angular.js") diff --git a/InventoryTraker.Web/App_Start/SeedData.cs b/InventoryTraker.Web/App_Start/SeedData.cs index 7bf3a8f..68e2eba 100644 --- a/InventoryTraker.Web/App_Start/SeedData.cs +++ b/InventoryTraker.Web/App_Start/SeedData.cs @@ -1,6 +1,8 @@ using System; using System.Collections.Generic; +using System.IO; using System.Linq; +using System.Web; using Humanizer; using InventoryTraker.Web.Core; using InventoryTraker.Web.Data; @@ -47,6 +49,10 @@ namespace InventoryTraker.Web if (!context.Inventories.Any()) { + AddInventoryTypes(context); + + context.SaveChanges(); + AddInventory(context); context.SaveChanges(); @@ -65,48 +71,59 @@ namespace InventoryTraker.Web } } + private static void AddInventoryTypes(AppDbContext context) + { + var folder = HttpContext.Current.Server.MapPath("~/App_Data"); + var inventoryTypeFile = Path.Combine(folder, "InventoryTypeSeedData.xlsx"); + var parser = new InventoryTypeParser(new FileInfo(inventoryTypeFile)); + foreach (var inventoryType in parser.Parse()) + { + context.InventoryTypes.Add(inventoryType); + } + } + private static void AddInventory(AppDbContext context) { - var peanutButterSmooth = new InventoryType - { - ContainerType = "18 oz jars", - Name = "Peanut Butter Smth 18", - Id = "100395", - UnitsPerCase = 12, - PricePerCase = 14.55M, - WeightPerCase = 13.5 - }; - context.InventoryTypes.Add(peanutButterSmooth); - - var beanInventoryType = new InventoryType - { - ContainerType = "#300 cans", - Name = "Beans, Veg 300", - Id = "100363", - UnitsPerCase = 24, - PricePerCase = 10.18M, - WeightPerCase = 24 - }; - context.InventoryTypes.Add(beanInventoryType); - - var corn = new InventoryType - { - ContainerType = "#300 cans", - Name = "Corn Kernel 300", - Id = "100311", - UnitsPerCase = 24, - PricePerCase = 10.03M, - WeightPerCase = 22.9 - }; - context.InventoryTypes.Add(corn); + var pork = context.InventoryTypes.First(it => it.Identifier == "100139"); + var beans = context.InventoryTypes.First(it => it.Identifier == "100363"); + var pb = context.InventoryTypes.First(it => it.Identifier == "100395"); context.Inventories.Add(new Inventory { - InventoryType = beanInventoryType, + InventoryType = pork, ExpirationDate = DateTime.Now.AddYears(1).AtMidnight(), - Memo = "French cut", + AddedDate = DateTime.Now.AddDays(-1).AtMidnight(), + Memo = "Hormel", Quantity = 10, - Transactions = new List { new Transaction { AddedQuantity = 10, Memo = "arrival", TransactionDate = DateTime.Now} } + Transactions = new List { new Transaction + { + AddedQuantity = 10, Memo = "arrival", TransactionDate = DateTime.Now.AddDays(-1).AtMidnight(), Timestamp = DateTime.Now + }} + }); + + context.Inventories.Add(new Inventory + { + InventoryType = beans, + ExpirationDate = DateTime.Now.AddMonths(4).AtMidnight(), + AddedDate = DateTime.Now.AddMonths(-2).AtMidnight(), + Memo = "Cut", + Quantity = 15, + Transactions = new List { new Transaction + { + AddedQuantity = 15, Memo = "arrival", TransactionDate = DateTime.Now.AddMonths(-2).AtMidnight(), Timestamp = DateTime.Now + }} + }); + + context.Inventories.Add(new Inventory + { + InventoryType = pb, + ExpirationDate = DateTime.Now.AddDays(300).AtMidnight(), + AddedDate = DateTime.Now.AddDays(-34).AtMidnight(), + Quantity = 700, + Transactions = new List { new Transaction + { + AddedQuantity = 700, Memo = "arrival", TransactionDate = DateTime.Now.AddDays(-34).AtMidnight(), Timestamp = DateTime.Now + }} }); } diff --git a/InventoryTraker.Web/Controllers/AuthenticationController.cs b/InventoryTraker.Web/Controllers/AuthenticationController.cs index e33481a..fbd4c5b 100644 --- a/InventoryTraker.Web/Controllers/AuthenticationController.cs +++ b/InventoryTraker.Web/Controllers/AuthenticationController.cs @@ -38,7 +38,7 @@ namespace InventoryTraker.Web.Controllers var identity = _userManager.CreateIdentity(user, DefaultAuthenticationTypes.ApplicationCookie); - _authManager.SignIn(new AuthenticationProperties { IsPersistent = false }, identity); + _authManager.SignIn(new AuthenticationProperties { IsPersistent = true }, identity); return Json(true); } diff --git a/InventoryTraker.Web/Controllers/ControllerBase.cs b/InventoryTraker.Web/Controllers/ControllerBase.cs index 5e90f48..3efc08b 100644 --- a/InventoryTraker.Web/Controllers/ControllerBase.cs +++ b/InventoryTraker.Web/Controllers/ControllerBase.cs @@ -1,4 +1,5 @@ -using System.Web.Mvc; +using System.Linq; +using System.Web.Mvc; using InventoryTraker.Web.ActionResults; namespace InventoryTraker.Web.Controllers @@ -9,5 +10,16 @@ namespace InventoryTraker.Web.Controllers { return new BetterJsonResult() {Data = model}; } + + protected JsonResult PackageModelStateErrors() + { + var betterJsonResult = new BetterJsonResult(); + foreach (var err in ModelState.Where(ms => ms.Value.Errors.Any())) + { + betterJsonResult.AddError( + err.Key + ": " + string.Join(", ", err.Value.Errors.Select(e => e.ErrorMessage))); + } + return betterJsonResult; + } } } \ No newline at end of file diff --git a/InventoryTraker.Web/Controllers/InventoryController.cs b/InventoryTraker.Web/Controllers/InventoryController.cs index 634f9ee..148fd85 100644 --- a/InventoryTraker.Web/Controllers/InventoryController.cs +++ b/InventoryTraker.Web/Controllers/InventoryController.cs @@ -1,4 +1,6 @@ -using System.Linq; +using System; +using System.Collections.Generic; +using System.Linq; using System.Web.Mvc; using AutoMapper; using AutoMapper.QueryableExtensions; @@ -8,25 +10,6 @@ using InventoryTraker.Web.Models; namespace InventoryTraker.Web.Controllers { - public class InventoryTypeController : ControllerBase - { - private readonly AppDbContext _context; - - public InventoryTypeController(AppDbContext context) - { - _context = context; - } - - public JsonResult All() - { - var viewModels = _context.InventoryTypes - .OrderByDescending(x => x.Name) - .ProjectTo(); - - return BetterJson(viewModels.ToArray()); - } - } - public class InventoryController : ControllerBase { private readonly AppDbContext _context; @@ -44,7 +27,7 @@ namespace InventoryTraker.Web.Controllers public JsonResult All() { var viewModels = _context.Inventories - .OrderByDescending(x => x.InventoryType.Name) + .OrderBy(x => x.InventoryType.Name) .ProjectTo() .ToArray(); @@ -53,9 +36,20 @@ namespace InventoryTraker.Web.Controllers public JsonResult Add(InventoryAddForm form) { + if (!ModelState.IsValid) + return PackageModelStateErrors(); + var inventory = Mapper.Map(form); inventory.InventoryType = _context.InventoryTypes.Find(form.InventoryTypeId); _context.Inventories.Add(inventory); + inventory.Transactions = new List(); + inventory.Transactions.Add(new Transaction + { + AddedQuantity = inventory.Quantity, + Memo = "Arrival", + Timestamp = DateTime.Now, + TransactionDate = inventory.AddedDate + }); _context.SaveChanges(); var model = Mapper.Map(inventory); diff --git a/InventoryTraker.Web/Controllers/InventoryTypeController.cs b/InventoryTraker.Web/Controllers/InventoryTypeController.cs new file mode 100644 index 0000000..b13c451 --- /dev/null +++ b/InventoryTraker.Web/Controllers/InventoryTypeController.cs @@ -0,0 +1,27 @@ +using System.Linq; +using System.Web.Mvc; +using AutoMapper.QueryableExtensions; +using InventoryTraker.Web.Data; +using InventoryTraker.Web.Models; + +namespace InventoryTraker.Web.Controllers +{ + public class InventoryTypeController : ControllerBase + { + private readonly AppDbContext _context; + + public InventoryTypeController(AppDbContext context) + { + _context = context; + } + + public JsonResult All() + { + var viewModels = _context.InventoryTypes + .OrderByDescending(x => x.Name) + .ProjectTo(); + + return BetterJson(viewModels.ToArray()); + } + } +} \ No newline at end of file diff --git a/InventoryTraker.Web/Core/Inventory.cs b/InventoryTraker.Web/Core/Inventory.cs index ad287fc..d1d553a 100644 --- a/InventoryTraker.Web/Core/Inventory.cs +++ b/InventoryTraker.Web/Core/Inventory.cs @@ -1,61 +1,27 @@ using System; -using System.Collections; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; -using System.ComponentModel.DataAnnotations.Schema; namespace InventoryTraker.Web.Core { // AKA Commodity - public class InventoryType - { - [DatabaseGenerated(DatabaseGeneratedOption.None)] - public string Id { get; set; } - - [Required] - public string Name { get; set; } - - [Required] - public int UnitsPerCase { get; set; } - - [Required] - public string ContainerType { get; set; } - - public double WeightPerCase { get; set; } - - public decimal PricePerCase { get; set; } - } - public class Inventory { public int Id { get; set; } public virtual InventoryType InventoryType { get; set; } - public virtual ICollection Transactions { get; set; } + public virtual ICollection Transactions { get; set; } [Required] public DateTime ExpirationDate { get; set; } + [Required] + public DateTime AddedDate { get; set; } + [Required] public int Quantity { get; set; } public string Memo { get; set; } } - - public class Transaction - { - public int Id { get; set; } - - public virtual Inventory Inventory { get; set; } - - public int AddedQuantity { get; set; } - - public int RemovedQuantity { get; set; } - - public DateTime TransactionDate { get; set; } - - public string Memo { get; set; } - - } } \ No newline at end of file diff --git a/InventoryTraker.Web/Core/InventoryType.cs b/InventoryTraker.Web/Core/InventoryType.cs new file mode 100644 index 0000000..37965a8 --- /dev/null +++ b/InventoryTraker.Web/Core/InventoryType.cs @@ -0,0 +1,25 @@ +using System.ComponentModel.DataAnnotations; + +namespace InventoryTraker.Web.Core +{ + public class InventoryType + { + public int Id { get; set; } + + [Required] + public string Identifier { get; set; } + + [Required] + public string Name { get; set; } + + [Required] + public int UnitsPerCase { get; set; } + + [Required] + public string ContainerType { get; set; } + + public double WeightPerCase { get; set; } + + public decimal PricePerCase { get; set; } + } +} \ No newline at end of file diff --git a/InventoryTraker.Web/Core/Transaction.cs b/InventoryTraker.Web/Core/Transaction.cs new file mode 100644 index 0000000..5814e7a --- /dev/null +++ b/InventoryTraker.Web/Core/Transaction.cs @@ -0,0 +1,26 @@ +using System; +using System.ComponentModel.DataAnnotations; + +namespace InventoryTraker.Web.Core +{ + public class Transaction + { + public int Id { get; set; } + + public virtual Inventory Inventory { get; set; } + + [Required] + public int AddedQuantity { get; set; } + + [Required] + public int RemovedQuantity { get; set; } + + [Required] + public DateTime TransactionDate { get; set; } + + public string Memo { get; set; } + + [Required] + public DateTime Timestamp { get; set; } + } +} \ No newline at end of file diff --git a/InventoryTraker.Web/Helpers/AngularModelHelper.cs b/InventoryTraker.Web/Helpers/AngularModelHelper.cs index adc8c08..434a237 100644 --- a/InventoryTraker.Web/Helpers/AngularModelHelper.cs +++ b/InventoryTraker.Web/Helpers/AngularModelHelper.cs @@ -35,9 +35,12 @@ namespace InventoryTraker.Web.Helpers /// Converts a lambda expression into a camel-cased AngularJS binding expression, ie: /// {{vm.model.parentProperty.childProperty}} /// - public IHtmlString BindingFor(Expression> property) + public IHtmlString BindingFor(Expression> property, string filter = "") { - return MvcHtmlString.Create("{{" + ExpressionForInternal(property) + "}}"); + return MvcHtmlString.Create("{{" + + ExpressionForInternal(property) + + (!string.IsNullOrEmpty(filter) ? " | " + filter : string.Empty) + + "}}"); } /// @@ -120,7 +123,7 @@ namespace InventoryTraker.Web.Helpers { //input.Attr("type", "date"); input.Attr("bs-datepicker"); - input.Attr("data-date-format", "dd/MM/yyyy"); + input.Attr("data-date-format", "d/M/yyyy"); } if (metadata.DataTypeName == "PhoneNumber") diff --git a/InventoryTraker.Web/InventoryTraker.Web.csproj b/InventoryTraker.Web/InventoryTraker.Web.csproj index dd1229c..8d19e03 100644 --- a/InventoryTraker.Web/InventoryTraker.Web.csproj +++ b/InventoryTraker.Web/InventoryTraker.Web.csproj @@ -47,6 +47,22 @@ ..\packages\AutoMapper.4.2.0\lib\net45\AutoMapper.dll True + + ..\packages\ClosedXML.0.76.0\lib\net40-client\ClosedXML.dll + True + + + ..\packages\CsvHelper.2.11.0\lib\net40-client\CsvHelper.dll + True + + + ..\packages\CsvHelper.Excel.1.0.5\lib\net40-client\CsvHelper.Excel.dll + True + + + ..\packages\DocumentFormat.OpenXml.2.5\lib\DocumentFormat.OpenXml.dll + True + ..\packages\EntityFramework.6.1.3\lib\net45\EntityFramework.dll True @@ -211,6 +227,7 @@ + @@ -234,7 +251,6 @@ - @@ -263,6 +279,10 @@ + + PreserveNewest + + @@ -289,6 +309,7 @@ + @@ -296,8 +317,10 @@ + + @@ -329,6 +352,8 @@ + + @@ -343,17 +368,17 @@ - - - + + + - - - - + + + + - + Web.config @@ -363,7 +388,6 @@ - diff --git a/InventoryTraker.Web/Models/InventoryAddForm.cs b/InventoryTraker.Web/Models/InventoryAddForm.cs index 0d445f8..8205d5d 100644 --- a/InventoryTraker.Web/Models/InventoryAddForm.cs +++ b/InventoryTraker.Web/Models/InventoryAddForm.cs @@ -9,12 +9,13 @@ namespace InventoryTraker.Web.Models public class InventoryAddForm : IMapTo { [HiddenInput(DisplayValue = false)] - public string InventoryTypeId { get; set; } + [Required] + public int InventoryTypeId { get; set; } [Required] public DateTime ExpirationDate { get; set; } - [Required] + [Required, Range(1, int.MaxValue, ErrorMessage = "Quantity must be greater than 0")] public int Quantity { get; set; } [Required, Display(Name = "Arrival Date")] diff --git a/InventoryTraker.Web/Models/InventoryTypeViewModel.cs b/InventoryTraker.Web/Models/InventoryTypeViewModel.cs index 2f8c7ae..da8f12b 100644 --- a/InventoryTraker.Web/Models/InventoryTypeViewModel.cs +++ b/InventoryTraker.Web/Models/InventoryTypeViewModel.cs @@ -5,7 +5,9 @@ namespace InventoryTraker.Web.Models { public class InventoryTypeViewModel : IMapFrom { - public string Id { get; set; } + public int Id { get; set; } + + public string Identifier { get; set; } public string Name { get; set; } diff --git a/InventoryTraker.Web/Models/InventoryViewModel.cs b/InventoryTraker.Web/Models/InventoryViewModel.cs index d8ca5a5..c22dabe 100644 --- a/InventoryTraker.Web/Models/InventoryViewModel.cs +++ b/InventoryTraker.Web/Models/InventoryViewModel.cs @@ -1,4 +1,5 @@ using System; +using System.Linq; using AutoMapper; using Heroic.AutoMapper; using InventoryTraker.Web.Core; @@ -23,13 +24,19 @@ namespace InventoryTraker.Web.Models public DateTime ExpirationDate { get; set; } + public DateTime AddedDate { get; set; } + + public string Memo { get; set; } + public void CreateMappings(IMapperConfiguration configuration) { configuration.CreateMap() .ForMember(d => d.Name, opt => opt.MapFrom(s => s.InventoryType.Name)) .ForMember(d => d.UnitsPerCase, opt => opt.MapFrom(s => s.InventoryType.UnitsPerCase)) + .ForMember(d => d.ContainerType, opt => opt.MapFrom(s => s.InventoryType.ContainerType)) .ForMember(d => d.WeightPerCase, opt => opt.MapFrom(s => s.InventoryType.WeightPerCase)) - .ForMember(d => d.PricePerCase, opt => opt.MapFrom(s => s.InventoryType.PricePerCase)); + .ForMember(d => d.PricePerCase, opt => opt.MapFrom(s => s.InventoryType.PricePerCase)) + .ForMember(d => d.AddedDate, opt => opt.MapFrom(s => s.AddedDate)); } } } \ No newline at end of file diff --git a/InventoryTraker.Web/Properties/PublishProfiles/ETHRA.pubxml b/InventoryTraker.Web/Properties/PublishProfiles/ETHRA.pubxml new file mode 100644 index 0000000..530d421 --- /dev/null +++ b/InventoryTraker.Web/Properties/PublishProfiles/ETHRA.pubxml @@ -0,0 +1,33 @@ + + + + + Package + Release + Any CPU + + True + False + C:\Users\poprhythm\Documents\code\PublishPackages\InventoryTraker.Web.zip + true + Default Web Site + + + + + + + + + + + + + + Data Source=localhost;Initial Catalog=InventoryTraker;User Id=InventoryTrakerUser;Password=QcXxvpztGp1;Connect Timeout=60 + + + \ No newline at end of file diff --git a/InventoryTraker.Web/Utilities/ExcelParserBase.cs b/InventoryTraker.Web/Utilities/ExcelParserBase.cs new file mode 100644 index 0000000..d534af7 --- /dev/null +++ b/InventoryTraker.Web/Utilities/ExcelParserBase.cs @@ -0,0 +1,55 @@ +using System; +using System.IO; +using CsvHelper; +using CsvHelper.Configuration; +using CsvHelper.Excel; + +namespace InventoryTraker.Web.Utilities +{ + public class ExcelParserBase : IDisposable + { + protected readonly CsvReader CsvReader; + + protected ExcelParserBase(FileSystemInfo excelFile, CsvConfiguration csvConfiguration) + { + CsvReader = OpenExcel(excelFile, csvConfiguration); + } + + protected ExcelParserBase(FileSystemInfo excelFile) : + this(excelFile, + new CsvConfiguration + { + HasHeaderRecord = false, + IgnoreBlankLines = false, + IgnoreReadingExceptions = true + }) + { + } + + internal static CsvReader OpenExcel(FileSystemInfo excelFile, CsvConfiguration csvConfiguration = null) + { + if (!excelFile.Exists) + throw new FileNotFoundException($"Cannot find file '{excelFile.Name}'"); + + var excelParser = + csvConfiguration != null + ? new ExcelParser(excelFile.FullName, csvConfiguration) + : new ExcelParser(excelFile.FullName); + return new CsvReader(excelParser); + } + + protected string[] GetNextCsvRowValues() + { + // get values from row + if (!CsvReader.Read()) + return null; + + return CsvReader.CurrentRecord; + } + + public void Dispose() + { + CsvReader.Dispose(); + } + } +} \ No newline at end of file diff --git a/InventoryTraker.Web/Utilities/InventoryTypeParser.cs b/InventoryTraker.Web/Utilities/InventoryTypeParser.cs new file mode 100644 index 0000000..d20d586 --- /dev/null +++ b/InventoryTraker.Web/Utilities/InventoryTypeParser.cs @@ -0,0 +1,43 @@ +using System.Collections.Generic; +using System.IO; +using System.Linq; +using ClosedXML.Excel; +using CsvHelper.Configuration; +using InventoryTraker.Web.Core; + +namespace InventoryTraker.Web.Utilities +{ + public class InventoryTypeParser + { + private readonly FileSystemInfo _excelFile; + + private sealed class InventoryTypeMap : CsvClassMap + { + public InventoryTypeMap() + { + Map(m => m.Identifier); + Map(m => m.Name); + Map(m => m.UnitsPerCase); + Map(m => m.ContainerType); + Map(m => m.WeightPerCase); + Map(m => m.PricePerCase); + } + } + + public InventoryTypeParser(FileSystemInfo excelFile) + { + _excelFile = excelFile; + } + + public IList Parse() + { + var csvConfiguration = + new CsvConfiguration { IsHeaderCaseSensitive = false, IgnoreReadingExceptions = true}; + csvConfiguration.RegisterClassMap(); + using (var reader = ExcelParserBase.OpenExcel(_excelFile, csvConfiguration)) + { + return reader.GetRecords().ToList(); + } + } + } +} \ No newline at end of file diff --git a/InventoryTraker.Web/Views/Inventory/Index.cshtml b/InventoryTraker.Web/Views/Inventory/Index.cshtml index 3ab8458..9a3481f 100644 --- a/InventoryTraker.Web/Views/Inventory/Index.cshtml +++ b/InventoryTraker.Web/Views/Inventory/Index.cshtml @@ -9,7 +9,5 @@ Inventory -
- -
+ \ No newline at end of file diff --git a/InventoryTraker.Web/Views/Shared/_Layout.cshtml b/InventoryTraker.Web/Views/Shared/_Layout.cshtml index f164e1b..7d6cd20 100644 --- a/InventoryTraker.Web/Views/Shared/_Layout.cshtml +++ b/InventoryTraker.Web/Views/Shared/_Layout.cshtml @@ -6,7 +6,7 @@ @ViewBag.Title - InventoryTraker - @Styles.Render("~/Content/all.css") + @Styles.Render("~/Content/all-styles") @RenderSection("Styles", required: false) @@ -17,7 +17,7 @@
- @(Request.IsAuthenticated ? Html.Partial("_NavigationHero") : Html.Partial("_NavigationNoAuth")) + @(Request.IsAuthenticated ? Html.Partial("_Navigation") : Html.Partial("_NavigationNoAuth"))
@RenderBody() @@ -26,7 +26,7 @@
- @Scripts.Render("~/js/all.js") + @Scripts.Render("~/js/all-javascript") @RenderSection("Scripts", required: false) diff --git a/InventoryTraker.Web/js/inventory/InventoryAddDirective.js b/InventoryTraker.Web/js/inventory/InventoryAddDirective.js index 5680433..6124137 100644 --- a/InventoryTraker.Web/js/inventory/InventoryAddDirective.js +++ b/InventoryTraker.Web/js/inventory/InventoryAddDirective.js @@ -14,18 +14,25 @@ controller.$inject = ['$scope', 'inventorySvc', 'inventoryTypeSvc']; function controller($scope, inventorySvc, inventoryTypeSvc) { var vm = this; - vm.add = add; + vm.add = add; vm.saving = false; vm.inventory = {}; vm.inventoryTypes = inventoryTypeSvc.inventoryTypes; vm.errorMessage = null; vm.quantity = quantity; + function zeroNaN(v) { + return isNaN(v) ? 0 : v; + } + function quantity() { vm.inventory.quantity = - $scope.palletCount * $scope.casesPerPallet + $scope.caseCount; - return vm.inventory.quantity; + zeroNaN($scope.palletCount) + * zeroNaN($scope.casesPerPallet) + + zeroNaN($scope.caseCount); + + return vm.inventory.quantity > 0 ? vm.inventory.quantity : ""; } function add() { @@ -36,14 +43,15 @@ $scope.$close(); }) .error(function(data) { - vm.errorMessage = 'There was a problem adding the inventory: ' + data; + vm.errorMessage = + 'There was a problem adding the inventory: ' + data.errorMessage; }) .finally(function() { vm.saving = false; }); } - $scope.$watch('commodity', function (newValue, oldValue) { + $scope.$watch('vm.commodity', function (newValue, oldValue) { if (newValue) vm.inventory.inventoryTypeId = newValue.id; }); diff --git a/InventoryTraker.Web/js/inventory/InventoryDetailsDirective.js b/InventoryTraker.Web/js/inventory/InventoryDetailsDirective.js index 05a161e..784939a 100644 --- a/InventoryTraker.Web/js/inventory/InventoryDetailsDirective.js +++ b/InventoryTraker.Web/js/inventory/InventoryDetailsDirective.js @@ -4,9 +4,7 @@ window.app.directive('inventoryDetails', inventoryDetails); function inventoryDetails() { return { - scope: { - inventory: '=' - }, + scope: {"inventories" : "="}, templateUrl: '/inventory/template/inventoryDetails.tmpl.cshtml', controller: controller, controllerAs: 'vm' @@ -17,9 +15,9 @@ function controller($scope, $uibModal) { var vm = this; - vm.inventory = $scope.inventory; + vm.inventories = $scope.inventories; vm.edit = edit; - + function edit() { $uibModal.open({ template: '', diff --git a/InventoryTraker.Web/js/inventory/InventoryListController.js b/InventoryTraker.Web/js/inventory/InventoryListController.js index 157e4d2..7f72fe9 100644 --- a/InventoryTraker.Web/js/inventory/InventoryListController.js +++ b/InventoryTraker.Web/js/inventory/InventoryListController.js @@ -11,7 +11,8 @@ function add() { $uibModal.open({ - template: '' + template: '', + backdrop: 'static' }); } } diff --git a/InventoryTraker.Web/js/inventory/inventorySvc.js b/InventoryTraker.Web/js/inventory/inventorySvc.js index ba9cc33..55ae0b1 100644 --- a/InventoryTraker.Web/js/inventory/inventorySvc.js +++ b/InventoryTraker.Web/js/inventory/inventorySvc.js @@ -42,56 +42,6 @@ if (inventories[i].Id == id) return inventories[i]; } - return null; - } - } -})(); - - -(function () { - window.app.factory('inventoryTypeSvc', inventoryTypeSvc); - - inventoryTypeSvc.$inject = ['$http']; - function inventoryTypeSvc($http) { - var inventoryTypes = []; - - loadInventoryTypes(); - - var svc = { - add: add, - update: update, - inventoryTypes: inventoryTypes, - getInventoryType: getInventoryType - }; - - return svc; - - function loadInventoryTypes() { - $http.post('/InventoryType/All') - .success(function (data) { - inventoryTypes.addRange(data); - }); - } - - function add(inventoryType) { - return $http.post('/InventoryType/Add', inventoryType) - .success(function (inventoryType) { - inventoryTypes.unshift(inventoryType); - }); - } - - function update(existingInventory, updatedInventory) { - return $http.post('/InventoryType/Update', updatedInventory) - .success(function (inventory) { - angular.extend(existingInventory, inventory); - }); - } - - function getInventoryType(id) { - for (var i = 0; i < inventoryTypes.length; i++) { - if (inventoryTypes[i].Id == id) return inventoryTypes[i]; - } - return null; } } diff --git a/InventoryTraker.Web/js/inventory/inventoryTypeSvc.js b/InventoryTraker.Web/js/inventory/inventoryTypeSvc.js new file mode 100644 index 0000000..5e30bd5 --- /dev/null +++ b/InventoryTraker.Web/js/inventory/inventoryTypeSvc.js @@ -0,0 +1,48 @@ +(function () { + window.app.factory('inventoryTypeSvc', inventoryTypeSvc); + + inventoryTypeSvc.$inject = ['$http']; + function inventoryTypeSvc($http) { + var inventoryTypes = []; + + loadInventoryTypes(); + + var svc = { + add: add, + update: update, + inventoryTypes: inventoryTypes, + getInventoryType: getInventoryType + }; + + return svc; + + function loadInventoryTypes() { + $http.post('/InventoryType/All') + .success(function (data) { + inventoryTypes.addRange(data); + }); + } + + function add(inventoryType) { + return $http.post('/InventoryType/Add', inventoryType) + .success(function (inventoryType) { + inventoryTypes.unshift(inventoryType); + }); + } + + function update(existingInventory, updatedInventory) { + return $http.post('/InventoryType/Update', updatedInventory) + .success(function (inventory) { + angular.extend(existingInventory, inventory); + }); + } + + function getInventoryType(id) { + for (var i = 0; i < inventoryTypes.length; i++) { + if (inventoryTypes[i].Id == id) return inventoryTypes[i]; + } + + return null; + } + } +})(); \ No newline at end of file diff --git a/InventoryTraker.Web/js/inventory/templates/inventoryAdd.tmpl.cshtml b/InventoryTraker.Web/js/inventory/templates/inventoryAdd.tmpl.cshtml index 0f6f58b..8dfe205 100644 --- a/InventoryTraker.Web/js/inventory/templates/inventoryAdd.tmpl.cshtml +++ b/InventoryTraker.Web/js/inventory/templates/inventoryAdd.tmpl.cshtml @@ -30,22 +30,22 @@
-
-
Selected Commodity
+
Commodity ID
-
{{commodity.id}}
-
Name
-
{{commodity.name}}
+
{{vm.commodity.identifier}}
Units per Case
-
{{commodity.unitsPerCase}} / {{commodity.containerType}}
+
{{vm.commodity.unitsPerCase}} / {{vm.commodity.containerType}}
@@ -57,28 +57,28 @@
-
Pallets
+
- +
-
Cases per Pallet
+
- +

-
Individual Cases
+
- +

-
Total Units
-
{{vm.quantity()}}
+
Total Units
+
{{vm.quantity()}}
diff --git a/InventoryTraker.Web/js/inventory/templates/inventoryDetails.tmpl.cshtml b/InventoryTraker.Web/js/inventory/templates/inventoryDetails.tmpl.cshtml index d5589c1..4ca79b3 100644 --- a/InventoryTraker.Web/js/inventory/templates/inventoryDetails.tmpl.cshtml +++ b/InventoryTraker.Web/js/inventory/templates/inventoryDetails.tmpl.cshtml @@ -1,12 +1,30 @@ @using InventoryTraker.Web.Helpers @model InventoryTraker.Web.Models.InventoryViewModel @{ - var inventory = Html.Angular().ModelFor("vm.inventory"); + var inventory = Html.Angular().ModelFor("inventory"); } -
-
- @inventory.BindingFor(x => x.Name) -
- Quantity - @inventory.BindingFor(x => x.Quantity) -
+ + + + + + + + + + + + + + + + + + + + + + + + +
NameUnits per CaseCase QtyUnit QtyExp DateAdded DateMemo
@inventory.BindingFor(x => x.Name) @inventory.BindingFor(x => x.UnitsPerCase) / @inventory.BindingFor(x => x.ContainerType)@inventory.BindingFor(x => x.Quantity){{inventory.quantity * inventory.unitsPerCase}}@inventory.BindingFor(x => x.ExpirationDate, "date:'shortDate'")@inventory.BindingFor(x => x.AddedDate, "date:'shortDate'")@inventory.BindingFor(x => x.Memo)
diff --git a/InventoryTraker.Web/packages.config b/InventoryTraker.Web/packages.config index cabeafa..053c902 100644 --- a/InventoryTraker.Web/packages.config +++ b/InventoryTraker.Web/packages.config @@ -8,6 +8,10 @@ + + + + diff --git a/InventoryTraker.sln b/InventoryTraker.sln index 2f7d52a..caa6a9d 100644 --- a/InventoryTraker.sln +++ b/InventoryTraker.sln @@ -5,6 +5,8 @@ VisualStudioVersion = 14.0.25123.0 MinimumVisualStudioVersion = 10.0.40219.1 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "InventoryTraker.Web", "InventoryTraker.Web\InventoryTraker.Web.csproj", "{5E5867A4-6152-4655-A04B-1737DF493A41}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "InventoryTraker.Web.Tests", "InventoryTraker.Web.Tests\InventoryTraker.Web.Tests.csproj", "{03B3BDF2-B2BE-42C1-8D6F-2B4E106A1D4B}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -15,6 +17,10 @@ Global {5E5867A4-6152-4655-A04B-1737DF493A41}.Debug|Any CPU.Build.0 = Debug|Any CPU {5E5867A4-6152-4655-A04B-1737DF493A41}.Release|Any CPU.ActiveCfg = Release|Any CPU {5E5867A4-6152-4655-A04B-1737DF493A41}.Release|Any CPU.Build.0 = Release|Any CPU + {03B3BDF2-B2BE-42C1-8D6F-2B4E106A1D4B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {03B3BDF2-B2BE-42C1-8D6F-2B4E106A1D4B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {03B3BDF2-B2BE-42C1-8D6F-2B4E106A1D4B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {03B3BDF2-B2BE-42C1-8D6F-2B4E106A1D4B}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/global.json b/global.json new file mode 100644 index 0000000..3eb265a --- /dev/null +++ b/global.json @@ -0,0 +1,5 @@ +{ + "projects": [ + "wrap" + ] +} \ No newline at end of file diff --git a/wrap/InventoryTraker.Web/project.json b/wrap/InventoryTraker.Web/project.json new file mode 100644 index 0000000..67cd84a --- /dev/null +++ b/wrap/InventoryTraker.Web/project.json @@ -0,0 +1,52 @@ +{ + "version": "1.0.0-*", + "frameworks": { + "net451": { + "wrappedProject": "../../InventoryTraker.Web/InventoryTraker.Web.csproj", + "bin": { + "assembly": "../../InventoryTraker.Web/obj/{configuration}/InventoryTraker.Web.dll", + "pdb": "../../InventoryTraker.Web/obj/{configuration}/InventoryTraker.Web.pdb" + }, + "dependencies": { + "Angular.UI.Bootstrap": "1.3.3", + "AngularJS.Animate": "1.5.8", + "AngularJS.Core": "1.5.8", + "angular-strap": "2.3.1", + "angular-ui-grid": "3.1.1", + "Antlr": "3.5.0.2", + "AutoMapper": "4.2.0", + "bootstrap": "3.3.7", + "EntityFramework": "6.1.3", + "FontAwesome": "4.6.3", + "Heroic.AutoMapper": "2.0.0", + "Heroic.Web.IoC": "4.1.2", + "HtmlTags": "3.0.0.186", + "Humanizer": "1.33.7", + "jQuery": "1.9.1", + "Microsoft.AspNet.Identity.Core": "2.2.1", + "Microsoft.AspNet.Identity.EntityFramework": "2.2.1", + "Microsoft.AspNet.Identity.Owin": "2.2.1", + "Microsoft.AspNet.Mvc": "5.2.3", + "Microsoft.AspNet.Mvc.Futures": "5.0.0", + "Microsoft.AspNet.Razor": "3.2.3", + "Microsoft.AspNet.Web.Optimization": "1.1.3", + "Microsoft.AspNet.WebApi": "5.2.3", + "Microsoft.AspNet.WebApi.Client": "5.2.3", + "Microsoft.AspNet.WebApi.Core": "5.2.3", + "Microsoft.AspNet.WebApi.WebHost": "5.2.3", + "Microsoft.AspNet.WebPages": "3.2.3", + "Microsoft.Owin": "3.0.1", + "Microsoft.Owin.Host.SystemWeb": "3.0.1", + "Microsoft.Owin.Security": "3.0.1", + "Microsoft.Owin.Security.Cookies": "3.0.1", + "Microsoft.Owin.Security.OAuth": "3.0.1", + "Microsoft.Web.Infrastructure": "1.0.0.0", + "Newtonsoft.Json": "6.0.7", + "Owin": "1.0", + "StructureMap": "4.4.0", + "WebActivatorEx": "2.1.0", + "WebGrease": "1.6.0" + } + } + } +} \ No newline at end of file