diff --git a/InventoryTraker.Web/App_Start/SeedData.cs b/InventoryTraker.Web/App_Start/SeedData.cs index d8e7aea..8dda30b 100644 --- a/InventoryTraker.Web/App_Start/SeedData.cs +++ b/InventoryTraker.Web/App_Start/SeedData.cs @@ -79,6 +79,7 @@ namespace InventoryTraker.Web for (var dd = DateTime.Today.AddYears(-4); dd < DateTime.Today.AddMonths(-2); dd = dd.AddMonths(3)) { + ExprireLossInventory(context, dd, r); // add some inventory for (int i = 0; i < r.Next(5,10); i++) { @@ -113,50 +114,13 @@ namespace InventoryTraker.Web } dd = dd.AddDays(14); // expire/loss some inventory - var availableInventories = - context.Inventories.Local.Where(i => i.Quantity > 0).ToList(); - - foreach (var inventory in availableInventories) - { - if (inventory.ExpirationDate <= dd && r.Next(0, 3) > 0) - { - var expiredTransaction = new Transaction - { - TransactionType = TransactionType.Expired, - RemovedQuantity = inventory.Quantity, - CurrentQuantity = 0, - Inventory = inventory, - Memo = $"Expired on {inventory.ExpirationDate.ToShortDateString()}", - TransactionDate = dd, - Timestamp = dd - }; - inventory.Quantity = expiredTransaction.CurrentQuantity; - inventory.Transactions.Add(expiredTransaction); - } - else if (r.Next(0, 20) == 0) - { - var lossQty = r.Next(1, inventory.Quantity + 1); - var lossTransaction = new Transaction - { - TransactionType = TransactionType.Loss, - RemovedQuantity = lossQty, - CurrentQuantity = inventory.Quantity - lossQty, - Inventory = inventory, - Memo = $"Missing", - TransactionDate = dd, - Timestamp = dd - }; - inventory.Quantity = lossTransaction.CurrentQuantity; - inventory.Transactions.Add(lossTransaction); - } - } - + ExprireLossInventory(context, dd, r); dd = dd.AddDays(14); // distribute some inventory foreach (var destination in counties) { - availableInventories = + var availableInventories = context.Inventories.Local.Where(i => i.Quantity > 0).ToList(); for ( @@ -189,6 +153,7 @@ namespace InventoryTraker.Web inventory.Transactions.Add(distributeTransaction); } } + ExprireLossInventory(context, dd, r); } //for (int i = 0; i < 200; i++) @@ -282,5 +247,46 @@ namespace InventoryTraker.Web // } //} } + + private static void ExprireLossInventory(AppDbContext context, DateTime dd, Random r) + { + var availableInventories = + context.Inventories.Local.Where(i => i.Quantity > 0).ToList(); + + foreach (var inventory in availableInventories) + { + if (inventory.ExpirationDate <= dd && r.Next(0, 4) > 0) + { + var expiredTransaction = new Transaction + { + TransactionType = TransactionType.Expired, + RemovedQuantity = inventory.Quantity, + CurrentQuantity = 0, + Inventory = inventory, + Memo = $"Expired on {inventory.ExpirationDate.ToShortDateString()}", + TransactionDate = dd, + Timestamp = dd + }; + inventory.Quantity = expiredTransaction.CurrentQuantity; + inventory.Transactions.Add(expiredTransaction); + } + else if (r.Next(0, 40) == 0) + { + var lossQty = r.Next(1, inventory.Quantity + 1); + var lossTransaction = new Transaction + { + TransactionType = TransactionType.Loss, + RemovedQuantity = lossQty, + CurrentQuantity = inventory.Quantity - lossQty, + Inventory = inventory, + Memo = $"Missing", + TransactionDate = dd, + Timestamp = dd + }; + inventory.Quantity = lossTransaction.CurrentQuantity; + inventory.Transactions.Add(lossTransaction); + } + } + } } } \ No newline at end of file diff --git a/InventoryTraker.Web/Controllers/ReportController.cs b/InventoryTraker.Web/Controllers/ReportController.cs index 0ceeaee..12689ed 100644 --- a/InventoryTraker.Web/Controllers/ReportController.cs +++ b/InventoryTraker.Web/Controllers/ReportController.cs @@ -7,6 +7,7 @@ using AutoMapper.QueryableExtensions; using InventoryTraker.Web.Core; using InventoryTraker.Web.Data; using InventoryTraker.Web.Models; +using InventoryTraker.Web.Utilities; namespace InventoryTraker.Web.Controllers { @@ -40,8 +41,6 @@ namespace InventoryTraker.Web.Controllers Date = g.Key.TransactionDate, Destination = g.Key.Destination, Transactions = g.ToList() - //(from transaction in g - //select Mapper.Map(transaction)).ToArray() }; var report = @@ -66,7 +65,143 @@ namespace InventoryTraker.Web.Controllers public ActionResult MonthlyInventory(DateTime month) { - return BetterJson(true); + var startDate = month; + var endDate = startDate.AddMonths(1); + + var inventoryTypeReport + = new InventoryTypeReport + { + Items = GetInventoryTypeReportItems(startDate, endDate), + Month = month + }; + + return BetterJson(inventoryTypeReport); + } + + private IEnumerable GetInventoryTypeReportItems(DateTime startDate, DateTime endDate) + { + var transactionsMostRecentBefore = + (from transaction in _context.Transactions + where + transaction.TransactionDate < startDate + group transaction by transaction.Inventory + into g + let mostRecent = + g.OrderByDescending(t => t.TransactionDate) + .ThenBy(t => t.CurrentQuantity) // for days with multiple, assume it's the smallest + .FirstOrDefault() + where mostRecent.CurrentQuantity > 0 + select mostRecent).ToList(); + + var transactionSums = + (from transaction in _context.Transactions + where + transaction.TransactionDate >= startDate + && transaction.TransactionDate < endDate + group transaction by transaction.Inventory + into g + let addedQty = g.Sum(t => t.AddedQuantity) + let distributed = g.Where(t => t.TransactionType == TransactionType.Distributed) + let distributedQty = distributed.Any() ? distributed.Sum(t => t.RemovedQuantity) : 0 + let adjustment = g.Where(t => + t.TransactionType == TransactionType.Expired + || t.TransactionType == TransactionType.Loss) + let adjustmentQty = adjustment.Any() ? adjustment.Sum(t => t.RemovedQuantity) : 0 + let endingQty = + g + .OrderByDescending(t => t.TransactionDate) + .ThenBy(t => t.CurrentQuantity) + .FirstOrDefault().CurrentQuantity + select new + { + Inventory = g.Key, + addedQty, + adjustmentQty, + distributedQty, + endingQty + }).ToList(); + + var inventoryReportItems = + transactionsMostRecentBefore.FullOuterJoin( // source + transactionSums, // inner + before => before.Inventory.Id, // fk + sums => sums.Inventory.Id, // pk + (before, sums, r) => + { + var item = new InventoryReportItem(); + + if (before != null) + { + item.Inventory = before.Inventory; + item.BeginningQuantity = before.CurrentQuantity; + if (sums != null) + { + item.AddedQuantity = sums.addedQty; + item.DistributedQuantity = sums.distributedQty; + item.AdjustmentQuantity = sums.adjustmentQty; + item.EndingQuantity = sums.endingQty; + } + else // no change + { + item.EndingQuantity = item.BeginningQuantity; + } + } + else if (sums != null) // item was added in this time period + { + item.Inventory = sums.Inventory; + item.AddedQuantity = sums.addedQty; + item.DistributedQuantity = sums.distributedQty; + item.AdjustmentQuantity = sums.adjustmentQty; + item.EndingQuantity = sums.endingQty; + } + item.TotalAvailableQuantity = item.BeginningQuantity + item.AddedQuantity; + return item; + }).ToArray(); + + // group by inventory type + var inventoryTypeReportItems = + from item in inventoryReportItems + group item by item.Inventory.InventoryType + into grp + select new InventoryTypeReportItem + { + InventoryType = Mapper.Map(grp.Key), + BeginningQuantity = grp.Sum(g => g.BeginningQuantity), + AddedQuantity = grp.Sum(g => g.AddedQuantity), + TotalAvailableQuantity = grp.Sum(g => g.TotalAvailableQuantity), + DistributedQuantity = grp.Sum(g => g.DistributedQuantity), + AdjustmentQuantity = grp.Sum(g => g.AdjustmentQuantity), + EndingQuantity = grp.Sum(g => g.EndingQuantity) + }; + return inventoryTypeReportItems; + } + + public class InventoryReportItem + { + public Inventory Inventory { get; set; } + public int BeginningQuantity { get; set; } + public int AddedQuantity { get; set; } + public int TotalAvailableQuantity { get; set; } + public int DistributedQuantity { get; set; } + public int AdjustmentQuantity { get; set; } + public int EndingQuantity { get; set; } + } + + public class InventoryTypeReport + { + public DateTime Month { get; set; } + public IEnumerable Items { get; set; } + } + + public class InventoryTypeReportItem + { + public InventoryTypeViewModel InventoryType { get; set; } + public int BeginningQuantity { get; set; } + public int AddedQuantity { get; set; } + public int TotalAvailableQuantity { get; set; } + public int DistributedQuantity { get; set; } + public int AdjustmentQuantity { get; set; } + public int EndingQuantity { get; set; } } } } \ No newline at end of file diff --git a/InventoryTraker.Web/InventoryTraker.Web.csproj b/InventoryTraker.Web/InventoryTraker.Web.csproj index 908f56b..5b85d78 100644 --- a/InventoryTraker.Web/InventoryTraker.Web.csproj +++ b/InventoryTraker.Web/InventoryTraker.Web.csproj @@ -246,14 +246,18 @@ + - + + + + @@ -300,10 +304,12 @@ Always - + + + Designer @@ -371,6 +377,7 @@ + diff --git a/InventoryTraker.Web/Models/InventoryViewModel.cs b/InventoryTraker.Web/Models/InventoryViewModel.cs index 3b54a72..476908c 100644 --- a/InventoryTraker.Web/Models/InventoryViewModel.cs +++ b/InventoryTraker.Web/Models/InventoryViewModel.cs @@ -9,6 +9,8 @@ namespace InventoryTraker.Web.Models { public int Id { get; set; } + public int InventoryTypeId { get; set; } + public string Name { get; set; } public int UnitsPerCase { get; set; } @@ -32,6 +34,7 @@ namespace InventoryTraker.Web.Models public void CreateMappings(IMapperConfiguration configuration) { configuration.CreateMap() + .ForMember(d => d.InventoryTypeId, opt => opt.MapFrom(s => s.InventoryType.Id)) .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)) diff --git a/InventoryTraker.Web/Utilities/IEnumerableExtensions.cs b/InventoryTraker.Web/Utilities/IEnumerableExtensions.cs new file mode 100644 index 0000000..a5fe541 --- /dev/null +++ b/InventoryTraker.Web/Utilities/IEnumerableExtensions.cs @@ -0,0 +1,57 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace InventoryTraker.Web.Utilities +{ + public static class IEnumerableExtensions + { + public static IEnumerable FullOuterGroupJoin( + this IEnumerable a, + IEnumerable b, + Func selectKeyA, + Func selectKeyB, + Func, IEnumerable, TKey, TResult> projection, + IEqualityComparer cmp = null) + { + cmp = cmp ?? EqualityComparer.Default; + var alookup = a.ToLookup(selectKeyA, cmp); + var blookup = b.ToLookup(selectKeyB, cmp); + + var keys = new HashSet(alookup.Select(p => p.Key), cmp); + keys.UnionWith(blookup.Select(p => p.Key)); + + var join = from key in keys + let xa = alookup[key] + let xb = blookup[key] + select projection(xa, xb, key); + + return join; + } + + public static IEnumerable FullOuterJoin( + this IEnumerable a, + IEnumerable b, + Func selectKeyA, + Func selectKeyB, + Func projection, + TA defaultA = default(TA), + TB defaultB = default(TB), + IEqualityComparer cmp = null) + { + cmp = cmp ?? EqualityComparer.Default; + var alookup = a.ToLookup(selectKeyA, cmp); + var blookup = b.ToLookup(selectKeyB, cmp); + + var keys = new HashSet(alookup.Select(p => p.Key), cmp); + keys.UnionWith(blookup.Select(p => p.Key)); + + var join = from key in keys + from xa in alookup[key].DefaultIfEmpty(defaultA) + from xb in blookup[key].DefaultIfEmpty(defaultB) + select projection(xa, xb, key); + + return join; + } + } +} \ No newline at end of file diff --git a/InventoryTraker.Web/Views/Report/Distribution.cshtml b/InventoryTraker.Web/Views/Report/Distribution.cshtml index 54887e4..1f12e62 100644 --- a/InventoryTraker.Web/Views/Report/Distribution.cshtml +++ b/InventoryTraker.Web/Views/Report/Distribution.cshtml @@ -11,7 +11,7 @@
- +
diff --git a/InventoryTraker.Web/Views/Report/MonthlyInventory.cshtml b/InventoryTraker.Web/Views/Report/MonthlyInventory.cshtml index 0a7e2d6..0e97c23 100644 --- a/InventoryTraker.Web/Views/Report/MonthlyInventory.cshtml +++ b/InventoryTraker.Web/Views/Report/MonthlyInventory.cshtml @@ -11,14 +11,15 @@
- +
-
+
- +

{{vm.monthlyInventoryData.month | date:'MMMM yyyy'}}

+
diff --git a/InventoryTraker.Web/js/app.js b/InventoryTraker.Web/js/app.js index 8bfad5f..ea8de6e 100644 --- a/InventoryTraker.Web/js/app.js +++ b/InventoryTraker.Web/js/app.js @@ -1,5 +1,14 @@ (function() { 'use strict'; - window.app = angular.module('InventoryTraker', ['ngAnimate', 'ui.bootstrap', 'ui.grid', 'ui.grid.pagination', 'mgcrea.ngStrap']); + window.app = + angular + .module('InventoryTraker', ['ngAnimate', 'ui.bootstrap', 'ui.grid', 'ui.grid.pagination', 'mgcrea.ngStrap']) + .config(function($datepickerProvider) { + angular.extend($datepickerProvider.defaults, + { + iconLeft: 'fa fa-chevron-left', + iconRight: 'fa fa-chevron-right' + }); + }); })(); \ No newline at end of file diff --git a/InventoryTraker.Web/js/inventory/templates/inventoryEdit.tmpl.cshtml b/InventoryTraker.Web/js/inventory/templates/inventoryEdit.tmpl.cshtml index be8bb0a..062ce13 100644 --- a/InventoryTraker.Web/js/inventory/templates/inventoryEdit.tmpl.cshtml +++ b/InventoryTraker.Web/js/inventory/templates/inventoryEdit.tmpl.cshtml @@ -36,7 +36,7 @@ - + {{transaction.transactionType}} {{transaction.memo}} {{transaction.transactionDate | date:'shortDate'}} @@ -53,8 +53,8 @@ - Warning: Deleting the only transaction will delete the entire inventory - + Warning: Deleting the only transaction will delete the entire inventory + + + \ No newline at end of file