Inventory and Type reports
This commit is contained in:
@@ -0,0 +1,16 @@
|
|||||||
|
using Heroic.AutoMapper;
|
||||||
|
using InventoryTraker.Web.Core;
|
||||||
|
using NUnit.Framework;
|
||||||
|
|
||||||
|
namespace InventoryTraker.Web.Tests
|
||||||
|
{
|
||||||
|
[SetUpFixture]
|
||||||
|
public class Setup
|
||||||
|
{
|
||||||
|
[OneTimeSetUp]
|
||||||
|
public void S()
|
||||||
|
{
|
||||||
|
HeroicAutoMapperConfigurator.LoadMapsFromAssemblyContainingTypeAndReferencedAssemblies<Inventory>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -63,6 +63,8 @@
|
|||||||
<Compile Include="App_Start\AutoMapperConfig.cs" />
|
<Compile Include="App_Start\AutoMapperConfig.cs" />
|
||||||
<Compile Include="Models\InventoryAddForm.cs" />
|
<Compile Include="Models\InventoryAddForm.cs" />
|
||||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||||
|
<Compile Include="App_Start\Setup.cs" />
|
||||||
|
<Compile Include="Utilities\InventoryReportWriterTests.cs" />
|
||||||
<Compile Include="Utilities\DistributionReportWriterTests.cs" />
|
<Compile Include="Utilities\DistributionReportWriterTests.cs" />
|
||||||
<Compile Include="Utilities\MovementReportWriterTests.cs" />
|
<Compile Include="Utilities\MovementReportWriterTests.cs" />
|
||||||
<Compile Include="Utilities\InventoryTypeParserTests.cs" />
|
<Compile Include="Utilities\InventoryTypeParserTests.cs" />
|
||||||
|
|||||||
@@ -11,12 +11,6 @@ namespace InventoryTraker.Web.Tests.Utilities
|
|||||||
[TestFixture]
|
[TestFixture]
|
||||||
public class DistributionReportWriterTests
|
public class DistributionReportWriterTests
|
||||||
{
|
{
|
||||||
[OneTimeSetUp]
|
|
||||||
public void StartUp()
|
|
||||||
{
|
|
||||||
HeroicAutoMapperConfigurator.LoadMapsFromAssemblyContainingTypeAndReferencedAssemblies<Inventory>();
|
|
||||||
}
|
|
||||||
|
|
||||||
private readonly DistributionReport[] _distributionReports =
|
private readonly DistributionReport[] _distributionReports =
|
||||||
{
|
{
|
||||||
new DistributionReport
|
new DistributionReport
|
||||||
|
|||||||
@@ -0,0 +1,43 @@
|
|||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using Heroic.AutoMapper;
|
||||||
|
using InventoryTraker.Web.Core;
|
||||||
|
using InventoryTraker.Web.Models;
|
||||||
|
using InventoryTraker.Web.Utilities;
|
||||||
|
using NUnit.Framework;
|
||||||
|
|
||||||
|
namespace InventoryTraker.Web.Tests.Utilities
|
||||||
|
{
|
||||||
|
[TestFixture]
|
||||||
|
public class InventoryReportWriterTests
|
||||||
|
{
|
||||||
|
private readonly InventoryViewModel[] _inventory =
|
||||||
|
{
|
||||||
|
new InventoryViewModel
|
||||||
|
{
|
||||||
|
|
||||||
|
Name = "Beans",
|
||||||
|
ContainerType = "#300 cans",
|
||||||
|
UnitsPerCase = 24,
|
||||||
|
AddedDate = new DateTime(2015,3,1),
|
||||||
|
ExpirationDate = new DateTime(2017,4,1),
|
||||||
|
Quantity = 20,
|
||||||
|
Memo = "my memo",
|
||||||
|
PricePerCase = 12.12M,
|
||||||
|
WeightPerCase = 20.1,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
[Test, Explicit]
|
||||||
|
public void Write()
|
||||||
|
{
|
||||||
|
using
|
||||||
|
(var outputFile
|
||||||
|
= new StreamWriter(Path.Combine(@"c:\temp", "InventoryReport.xlsx")))
|
||||||
|
{
|
||||||
|
var writer = new InventoryReportWriter();
|
||||||
|
writer.WriteStream(_inventory, outputFile.BaseStream);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -11,12 +11,6 @@ namespace InventoryTraker.Web.Tests.Utilities
|
|||||||
[TestFixture]
|
[TestFixture]
|
||||||
public class MovementReportWriterTests
|
public class MovementReportWriterTests
|
||||||
{
|
{
|
||||||
[OneTimeSetUp]
|
|
||||||
public void StartUp()
|
|
||||||
{
|
|
||||||
HeroicAutoMapperConfigurator.LoadMapsFromAssemblyContainingTypeAndReferencedAssemblies<Inventory>();
|
|
||||||
}
|
|
||||||
|
|
||||||
private readonly MovementReport _movementReport
|
private readonly MovementReport _movementReport
|
||||||
= new MovementReport
|
= new MovementReport
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ using InventoryTraker.Web.Attributes;
|
|||||||
using InventoryTraker.Web.Core;
|
using InventoryTraker.Web.Core;
|
||||||
using InventoryTraker.Web.Data;
|
using InventoryTraker.Web.Data;
|
||||||
using InventoryTraker.Web.Models;
|
using InventoryTraker.Web.Models;
|
||||||
|
using InventoryTraker.Web.Utilities;
|
||||||
|
|
||||||
namespace InventoryTraker.Web.Controllers
|
namespace InventoryTraker.Web.Controllers
|
||||||
{
|
{
|
||||||
@@ -28,7 +29,7 @@ namespace InventoryTraker.Web.Controllers
|
|||||||
public JsonResult All()
|
public JsonResult All()
|
||||||
{
|
{
|
||||||
var viewModels =
|
var viewModels =
|
||||||
AllInventory()
|
CurrentInventory()
|
||||||
.ProjectTo<InventoryViewModel>()
|
.ProjectTo<InventoryViewModel>()
|
||||||
.ToArray();
|
.ToArray();
|
||||||
|
|
||||||
@@ -42,7 +43,7 @@ namespace InventoryTraker.Web.Controllers
|
|||||||
return BetterJson(viewModel);
|
return BetterJson(viewModel);
|
||||||
}
|
}
|
||||||
|
|
||||||
private IQueryable<Inventory> AllInventory()
|
private IQueryable<Inventory> CurrentInventory()
|
||||||
{
|
{
|
||||||
return _context
|
return _context
|
||||||
.Inventories
|
.Inventories
|
||||||
@@ -50,6 +51,26 @@ namespace InventoryTraker.Web.Controllers
|
|||||||
.OrderBy(x => x.InventoryType.Name);
|
.OrderBy(x => x.InventoryType.Name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ActionResult Export()
|
||||||
|
{
|
||||||
|
var writer = new InventoryReportWriter();
|
||||||
|
|
||||||
|
var viewModels =
|
||||||
|
CurrentInventory()
|
||||||
|
.ProjectTo<InventoryViewModel>()
|
||||||
|
.ToArray();
|
||||||
|
|
||||||
|
var excel = writer.Write(viewModels);
|
||||||
|
|
||||||
|
var filename = $"Inventory{DateTime.Today:yyyyMMdd}.xlsx";
|
||||||
|
|
||||||
|
return
|
||||||
|
new FileContentResult(excel, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
|
||||||
|
{
|
||||||
|
FileDownloadName = filename
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
[ActionLog]
|
[ActionLog]
|
||||||
public JsonResult Add(InventoryAddForm form)
|
public JsonResult Add(InventoryAddForm form)
|
||||||
{
|
{
|
||||||
@@ -134,7 +155,7 @@ namespace InventoryTraker.Web.Controllers
|
|||||||
|
|
||||||
_context.SaveChanges();
|
_context.SaveChanges();
|
||||||
|
|
||||||
return BetterJson(AllInventory()
|
return BetterJson(CurrentInventory()
|
||||||
.ProjectTo<InventoryViewModel>()
|
.ProjectTo<InventoryViewModel>()
|
||||||
.ToArray());
|
.ToArray());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ using AutoMapper.QueryableExtensions;
|
|||||||
using InventoryTraker.Web.Core;
|
using InventoryTraker.Web.Core;
|
||||||
using InventoryTraker.Web.Data;
|
using InventoryTraker.Web.Data;
|
||||||
using InventoryTraker.Web.Models;
|
using InventoryTraker.Web.Models;
|
||||||
|
using InventoryTraker.Web.Utilities;
|
||||||
|
|
||||||
namespace InventoryTraker.Web.Controllers
|
namespace InventoryTraker.Web.Controllers
|
||||||
{
|
{
|
||||||
@@ -33,6 +34,27 @@ namespace InventoryTraker.Web.Controllers
|
|||||||
return BetterJson(viewModels.ToArray());
|
return BetterJson(viewModels.ToArray());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ActionResult Export()
|
||||||
|
{
|
||||||
|
var writer = new InventoryTypeReportWriter();
|
||||||
|
|
||||||
|
var viewModels =
|
||||||
|
_context.InventoryTypes
|
||||||
|
.OrderByDescending(x => x.Name)
|
||||||
|
.ProjectTo<InventoryTypeViewModel>()
|
||||||
|
.ToArray();
|
||||||
|
|
||||||
|
var excel = writer.Write(viewModels);
|
||||||
|
|
||||||
|
var filename = $"CommodityTypes{DateTime.Today:yyyyMMdd}.xlsx";
|
||||||
|
|
||||||
|
return
|
||||||
|
new FileContentResult(excel, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
|
||||||
|
{
|
||||||
|
FileDownloadName = filename
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
public JsonResult Add(InventoryTypeViewModel form)
|
public JsonResult Add(InventoryTypeViewModel form)
|
||||||
{
|
{
|
||||||
if (!ModelState.IsValid)
|
if (!ModelState.IsValid)
|
||||||
|
|||||||
@@ -1,23 +1,18 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Web.Mvc;
|
using System.Web.Mvc;
|
||||||
using AutoMapper;
|
using InventoryTraker.Web.Services;
|
||||||
using AutoMapper.QueryableExtensions;
|
|
||||||
using InventoryTraker.Web.Core;
|
|
||||||
using InventoryTraker.Web.Data;
|
|
||||||
using InventoryTraker.Web.Models;
|
|
||||||
using InventoryTraker.Web.Utilities;
|
using InventoryTraker.Web.Utilities;
|
||||||
|
|
||||||
namespace InventoryTraker.Web.Controllers
|
namespace InventoryTraker.Web.Controllers
|
||||||
{
|
{
|
||||||
public class ReportController : ControllerBase
|
public class ReportController : ControllerBase
|
||||||
{
|
{
|
||||||
private readonly AppDbContext _context;
|
private readonly ReportService _reportService;
|
||||||
|
|
||||||
public ReportController(AppDbContext context)
|
public ReportController(ReportService reportService)
|
||||||
{
|
{
|
||||||
_context = context;
|
_reportService = reportService;
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet]
|
[HttpGet]
|
||||||
@@ -28,14 +23,14 @@ namespace InventoryTraker.Web.Controllers
|
|||||||
|
|
||||||
public ActionResult Distribution(DateTime startDate, DateTime endDate)
|
public ActionResult Distribution(DateTime startDate, DateTime endDate)
|
||||||
{
|
{
|
||||||
var report = GetDistributionReport(startDate, endDate);
|
var report = _reportService.GetDistributionReport(startDate, endDate);
|
||||||
|
|
||||||
return BetterJson(report.ToArray());
|
return BetterJson(report.ToArray());
|
||||||
}
|
}
|
||||||
|
|
||||||
public ActionResult DistributionExcel(DateTime startDate, DateTime endDate)
|
public ActionResult DistributionExcel(DateTime startDate, DateTime endDate)
|
||||||
{
|
{
|
||||||
var report = GetDistributionReport(startDate, endDate);
|
var report = _reportService.GetDistributionReport(startDate, endDate);
|
||||||
|
|
||||||
var writer = new DistributionReportWriter();
|
var writer = new DistributionReportWriter();
|
||||||
var excel = writer.Write(report);
|
var excel = writer.Write(report);
|
||||||
@@ -49,37 +44,6 @@ namespace InventoryTraker.Web.Controllers
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private DistributionReport[] GetDistributionReport(DateTime startDate, DateTime endDate)
|
|
||||||
{
|
|
||||||
var query =
|
|
||||||
from t in _context.Transactions
|
|
||||||
where
|
|
||||||
t.TransactionType == TransactionType.Distributed
|
|
||||||
&& t.TransactionDate >= startDate
|
|
||||||
&& t.TransactionDate < endDate
|
|
||||||
group t by new { t.TransactionDate, t.Destination }
|
|
||||||
into g
|
|
||||||
select new
|
|
||||||
{
|
|
||||||
Date = g.Key.TransactionDate,
|
|
||||||
Destination = g.Key.Destination,
|
|
||||||
Transactions = g.ToList()
|
|
||||||
};
|
|
||||||
|
|
||||||
var report =
|
|
||||||
from item in query.ToArray()
|
|
||||||
select new DistributionReport
|
|
||||||
{
|
|
||||||
Date = item.Date,
|
|
||||||
Destination = item.Destination,
|
|
||||||
Transactions =
|
|
||||||
Mapper.Map<IList<Transaction>, IList<TransactionViewModel>>
|
|
||||||
(item.Transactions).ToArray()
|
|
||||||
};
|
|
||||||
|
|
||||||
return report.ToArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpGet]
|
[HttpGet]
|
||||||
public ActionResult Movement()
|
public ActionResult Movement()
|
||||||
{
|
{
|
||||||
@@ -88,14 +52,14 @@ namespace InventoryTraker.Web.Controllers
|
|||||||
|
|
||||||
public ActionResult Movement(DateTime month)
|
public ActionResult Movement(DateTime month)
|
||||||
{
|
{
|
||||||
var report = GetMovementReport(month);
|
var report = _reportService.GetMovementReport(month);
|
||||||
|
|
||||||
return BetterJson(report);
|
return BetterJson(report);
|
||||||
}
|
}
|
||||||
|
|
||||||
public ActionResult MovementExcel(DateTime month)
|
public ActionResult MovementExcel(DateTime month)
|
||||||
{
|
{
|
||||||
var report = GetMovementReport(month);
|
var report = _reportService.GetMovementReport(month);
|
||||||
|
|
||||||
var writer = new MovementReportWriter();
|
var writer = new MovementReportWriter();
|
||||||
var excel = writer.Write(report);
|
var excel = writer.Write(report);
|
||||||
@@ -108,127 +72,5 @@ namespace InventoryTraker.Web.Controllers
|
|||||||
FileDownloadName = filename
|
FileDownloadName = filename
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private MovementReport GetMovementReport(DateTime month)
|
|
||||||
{
|
|
||||||
var startDate = month;
|
|
||||||
var endDate = startDate.AddMonths(1);
|
|
||||||
|
|
||||||
return
|
|
||||||
new MovementReport
|
|
||||||
{
|
|
||||||
Items = GetMovementReportItems(startDate, endDate),
|
|
||||||
Month = month
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private IEnumerable<MovementReportItem> GetMovementReportItems(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 MovementReportInventoryItem();
|
|
||||||
|
|
||||||
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 MovementReportItem
|
|
||||||
{
|
|
||||||
InventoryType = Mapper.Map<InventoryTypeViewModel>(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;
|
|
||||||
}
|
|
||||||
|
|
||||||
private class MovementReportInventoryItem
|
|
||||||
{
|
|
||||||
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; }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -384,7 +384,10 @@
|
|||||||
<Compile Include="Models\ProfileForm.cs" />
|
<Compile Include="Models\ProfileForm.cs" />
|
||||||
<Compile Include="Models\TransactionViewModel.cs" />
|
<Compile Include="Models\TransactionViewModel.cs" />
|
||||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||||
|
<Compile Include="Services\ReportService.cs" />
|
||||||
<Compile Include="Utilities\ControllerContextExtensions.cs" />
|
<Compile Include="Utilities\ControllerContextExtensions.cs" />
|
||||||
|
<Compile Include="Utilities\InventoryTypeReportWriter.cs" />
|
||||||
|
<Compile Include="Utilities\InventoryReportWriter.cs" />
|
||||||
<Compile Include="Utilities\ExcelParserBase.cs" />
|
<Compile Include="Utilities\ExcelParserBase.cs" />
|
||||||
<Compile Include="Utilities\IEnumerableExtensions.cs" />
|
<Compile Include="Utilities\IEnumerableExtensions.cs" />
|
||||||
<Compile Include="Utilities\InventoryTypeParser.cs" />
|
<Compile Include="Utilities\InventoryTypeParser.cs" />
|
||||||
|
|||||||
@@ -0,0 +1,174 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using AutoMapper;
|
||||||
|
using InventoryTraker.Web.Core;
|
||||||
|
using InventoryTraker.Web.Data;
|
||||||
|
using InventoryTraker.Web.Models;
|
||||||
|
using InventoryTraker.Web.Utilities;
|
||||||
|
|
||||||
|
namespace InventoryTraker.Web.Services
|
||||||
|
{
|
||||||
|
public class ReportService
|
||||||
|
{
|
||||||
|
private readonly AppDbContext _context;
|
||||||
|
|
||||||
|
public ReportService(AppDbContext context)
|
||||||
|
{
|
||||||
|
_context = context;
|
||||||
|
}
|
||||||
|
|
||||||
|
public DistributionReport[] GetDistributionReport(DateTime startDate, DateTime endDate)
|
||||||
|
{
|
||||||
|
var query =
|
||||||
|
from t in _context.Transactions
|
||||||
|
where
|
||||||
|
t.TransactionType == TransactionType.Distributed
|
||||||
|
&& t.TransactionDate >= startDate
|
||||||
|
&& t.TransactionDate < endDate
|
||||||
|
group t by new { t.TransactionDate, t.Destination }
|
||||||
|
into g
|
||||||
|
select new
|
||||||
|
{
|
||||||
|
Date = g.Key.TransactionDate,
|
||||||
|
Destination = g.Key.Destination,
|
||||||
|
Transactions = g.ToList()
|
||||||
|
};
|
||||||
|
|
||||||
|
var report =
|
||||||
|
from item in query.ToArray()
|
||||||
|
select new DistributionReport
|
||||||
|
{
|
||||||
|
Date = item.Date,
|
||||||
|
Destination = item.Destination,
|
||||||
|
Transactions =
|
||||||
|
Mapper.Map<IList<Transaction>, IList<TransactionViewModel>>
|
||||||
|
(item.Transactions).ToArray()
|
||||||
|
};
|
||||||
|
|
||||||
|
return report.ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
public MovementReport GetMovementReport(DateTime month)
|
||||||
|
{
|
||||||
|
var startDate = month;
|
||||||
|
var endDate = startDate.AddMonths(1);
|
||||||
|
|
||||||
|
return
|
||||||
|
new MovementReport
|
||||||
|
{
|
||||||
|
Items = GetMovementReportItems(startDate, endDate),
|
||||||
|
Month = month
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private IEnumerable<MovementReportItem> GetMovementReportItems(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 MovementReportInventoryItem();
|
||||||
|
|
||||||
|
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 MovementReportItem
|
||||||
|
{
|
||||||
|
InventoryType = Mapper.Map<InventoryTypeViewModel>(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;
|
||||||
|
}
|
||||||
|
|
||||||
|
private class MovementReportInventoryItem
|
||||||
|
{
|
||||||
|
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; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
using System;
|
using System.Collections.Generic;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using AutoMapper;
|
using AutoMapper;
|
||||||
|
|||||||
@@ -0,0 +1,53 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using ClosedXML.Excel;
|
||||||
|
using CsvHelper;
|
||||||
|
using CsvHelper.Configuration;
|
||||||
|
using CsvHelper.Excel;
|
||||||
|
using InventoryTraker.Web.Models;
|
||||||
|
|
||||||
|
namespace InventoryTraker.Web.Utilities
|
||||||
|
{
|
||||||
|
public class InventoryReportWriter
|
||||||
|
{
|
||||||
|
private sealed class InventoryViewModelMap : CsvClassMap<InventoryViewModel>
|
||||||
|
{
|
||||||
|
public InventoryViewModelMap()
|
||||||
|
{
|
||||||
|
Map(m => m.Name).Name("Name of Commodity");
|
||||||
|
Map(m => m.UnitsPerCase).Name("Units per Case");
|
||||||
|
Map(m => m.ContainerType).Name("Container Type");
|
||||||
|
Map(m => m.Quantity).Name("Case Quantity");
|
||||||
|
Map(m => m.ExpirationDate).Name("Expiration Date");
|
||||||
|
Map(m => m.AddedDate).Name("Added Date");
|
||||||
|
Map(m => m.Memo).Name("Memo");
|
||||||
|
Map(m => m.WeightPerCase).Name("Weight per Case");
|
||||||
|
Map(m => m.PricePerCase).Name("Price per Case");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] Write(IEnumerable<InventoryViewModel> items)
|
||||||
|
{
|
||||||
|
using (var stream = new MemoryStream())
|
||||||
|
{
|
||||||
|
WriteStream(items, stream);
|
||||||
|
return stream.ToArray();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void WriteStream(IEnumerable<InventoryViewModel> items, Stream stream)
|
||||||
|
{
|
||||||
|
using (var workbook = new XLWorkbook(XLEventTracking.Disabled))
|
||||||
|
{
|
||||||
|
var worksheet = workbook.AddWorksheet("Current Inventory");
|
||||||
|
using (var writer = new CsvWriter(new ExcelSerializer(worksheet)))
|
||||||
|
{
|
||||||
|
writer.Configuration.RegisterClassMap(new InventoryViewModelMap());
|
||||||
|
writer.WriteRecords(items.OrderBy(i => i.Name));
|
||||||
|
workbook.SaveAs(stream);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using ClosedXML.Excel;
|
||||||
|
using CsvHelper;
|
||||||
|
using CsvHelper.Configuration;
|
||||||
|
using CsvHelper.Excel;
|
||||||
|
using InventoryTraker.Web.Models;
|
||||||
|
|
||||||
|
namespace InventoryTraker.Web.Utilities
|
||||||
|
{
|
||||||
|
public class InventoryTypeReportWriter
|
||||||
|
{
|
||||||
|
private sealed class InventoryTypeViewModelMap : CsvClassMap<InventoryTypeViewModel>
|
||||||
|
{
|
||||||
|
public InventoryTypeViewModelMap()
|
||||||
|
{
|
||||||
|
Map(m => m.Identifier).Name("Identifier");
|
||||||
|
Map(m => m.Name).Name("Name of Commodity");
|
||||||
|
Map(m => m.UnitsPerCase).Name("Units per Case");
|
||||||
|
Map(m => m.ContainerType).Name("Container Type");
|
||||||
|
Map(m => m.WeightPerCase).Name("Weight per Case");
|
||||||
|
Map(m => m.PricePerCase).Name("Price per Case");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] Write(IEnumerable<InventoryTypeViewModel> items)
|
||||||
|
{
|
||||||
|
using (var stream = new MemoryStream())
|
||||||
|
{
|
||||||
|
WriteStream(items, stream);
|
||||||
|
return stream.ToArray();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void WriteStream(IEnumerable<InventoryTypeViewModel> items, Stream stream)
|
||||||
|
{
|
||||||
|
using (var workbook = new XLWorkbook(XLEventTracking.Disabled))
|
||||||
|
{
|
||||||
|
var worksheet = workbook.AddWorksheet("Commodity Types");
|
||||||
|
using (var writer = new CsvWriter(new ExcelSerializer(worksheet)))
|
||||||
|
{
|
||||||
|
writer.Configuration.RegisterClassMap(new InventoryTypeViewModelMap());
|
||||||
|
writer.WriteRecords(items.OrderBy(i => i.Name));
|
||||||
|
workbook.SaveAs(stream);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -10,6 +10,7 @@
|
|||||||
<div class="pull-left">
|
<div class="pull-left">
|
||||||
<a class="btn btn-default" href="" ng-click="vm.add()"><i class="fa fa-plus-circle"></i> Arrival</a>
|
<a class="btn btn-default" href="" ng-click="vm.add()"><i class="fa fa-plus-circle"></i> Arrival</a>
|
||||||
<a class="btn btn-default" href="" ng-click="vm.distribute()"><i class="fa fa-share-square"></i> Distribute</a>
|
<a class="btn btn-default" href="" ng-click="vm.distribute()"><i class="fa fa-share-square"></i> Distribute</a>
|
||||||
|
<a class="btn btn-default" href="" ng-click="vm.export()"><i class="fa fa-file-excel-o"></i> Export</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="pull-right">
|
<div class="pull-right">
|
||||||
<a class="btn btn-default" href="/InventoryType"><i class="fa fa-cube"></i> Commodity Types</a>
|
<a class="btn btn-default" href="/InventoryType"><i class="fa fa-cube"></i> Commodity Types</a>
|
||||||
|
|||||||
@@ -10,7 +10,8 @@
|
|||||||
</h1>
|
</h1>
|
||||||
<div class="pull-left">
|
<div class="pull-left">
|
||||||
<a class="btn btn-default" href="" ng-click="vm.add()"><i class="fa fa-plus-circle"></i> Add</a>
|
<a class="btn btn-default" href="" ng-click="vm.add()"><i class="fa fa-plus-circle"></i> Add</a>
|
||||||
</div>
|
<a class="btn btn-default" href="" ng-click="vm.export()"><i class="fa fa-file-excel-o"></i> Export</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
<inventory-type-list inventory-types="vm.inventoryTypes"></inventory-type-list>
|
<inventory-type-list inventory-types="vm.inventoryTypes"></inventory-type-list>
|
||||||
</div>
|
</div>
|
||||||
@@ -3,14 +3,14 @@
|
|||||||
|
|
||||||
window.app.controller('InventoryListController', InventoryListController);
|
window.app.controller('InventoryListController', InventoryListController);
|
||||||
|
|
||||||
InventoryListController.$inject = ['$uibModal', 'inventorySvc'];
|
InventoryListController.$inject = ['$uibModal', 'inventorySvc', 'downloadSvc'];
|
||||||
function InventoryListController($uibModal, inventorySvc) {
|
function InventoryListController($uibModal, inventorySvc, downloadSvc) {
|
||||||
var vm = this;
|
var vm = this;
|
||||||
|
|
||||||
vm.add = add;
|
vm.add = add;
|
||||||
vm.distribute = distribute;
|
vm.distribute = distribute;
|
||||||
vm.inventories = inventorySvc.inventories;
|
vm.inventories = inventorySvc.inventories;
|
||||||
|
|
||||||
function add() {
|
function add() {
|
||||||
$uibModal.open({
|
$uibModal.open({
|
||||||
template: '<inventory-add />',
|
template: '<inventory-add />',
|
||||||
@@ -24,5 +24,10 @@
|
|||||||
backdrop: 'static'
|
backdrop: 'static'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
vm.export = function () {
|
||||||
|
inventorySvc.exportInventory()
|
||||||
|
.success(downloadSvc.success);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
@@ -15,7 +15,8 @@
|
|||||||
inventories: inventories,
|
inventories: inventories,
|
||||||
get: get,
|
get: get,
|
||||||
refresh: refresh,
|
refresh: refresh,
|
||||||
find: find
|
find: find,
|
||||||
|
exportInventory: exportInventory
|
||||||
};
|
};
|
||||||
|
|
||||||
return svc;
|
return svc;
|
||||||
@@ -27,6 +28,10 @@
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function exportInventory() {
|
||||||
|
return $http.post('/Inventory/Export', {}, { responseType: 'arraybuffer' });
|
||||||
|
}
|
||||||
|
|
||||||
function add(inventory) {
|
function add(inventory) {
|
||||||
return $http.post('/Inventory/Add', inventory)
|
return $http.post('/Inventory/Add', inventory)
|
||||||
.success(function(inventory) {
|
.success(function(inventory) {
|
||||||
|
|||||||
@@ -3,8 +3,8 @@
|
|||||||
|
|
||||||
window.app.controller('InventoryTypeController', InventoryTypeController);
|
window.app.controller('InventoryTypeController', InventoryTypeController);
|
||||||
|
|
||||||
InventoryTypeController.$inject = ['$uibModal', 'inventoryTypeSvc'];
|
InventoryTypeController.$inject = ['$uibModal', 'inventoryTypeSvc', 'downloadSvc'];
|
||||||
function InventoryTypeController($uibModal, inventoryTypeSvc) {
|
function InventoryTypeController($uibModal, inventoryTypeSvc, downloadSvc) {
|
||||||
var vm = this;
|
var vm = this;
|
||||||
|
|
||||||
vm.add = add;
|
vm.add = add;
|
||||||
@@ -24,5 +24,10 @@
|
|||||||
backdrop: 'static'
|
backdrop: 'static'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
vm.export = function () {
|
||||||
|
inventoryTypeSvc.exportInventoryTypes()
|
||||||
|
.success(downloadSvc.success);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
@@ -10,7 +10,8 @@
|
|||||||
var svc = {
|
var svc = {
|
||||||
add: add,
|
add: add,
|
||||||
update: update,
|
update: update,
|
||||||
inventoryTypes: inventoryTypes
|
inventoryTypes: inventoryTypes,
|
||||||
|
exportInventoryTypes: exportInventoryTypes
|
||||||
};
|
};
|
||||||
|
|
||||||
return svc;
|
return svc;
|
||||||
@@ -22,6 +23,10 @@
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function exportInventoryTypes() {
|
||||||
|
return $http.post('/InventoryType/Export', {}, { responseType: 'arraybuffer' });
|
||||||
|
}
|
||||||
|
|
||||||
function add(inventoryType) {
|
function add(inventoryType) {
|
||||||
return $http.post('/InventoryType/Add', inventoryType)
|
return $http.post('/InventoryType/Add', inventoryType)
|
||||||
.success(function (inventoryType) {
|
.success(function (inventoryType) {
|
||||||
|
|||||||
Reference in New Issue
Block a user