Add filtering

Improve matching
This commit is contained in:
2015-09-25 11:15:57 -04:00
parent 5e90c6d330
commit 46dfaba731
14 changed files with 157 additions and 86 deletions
+9 -21
View File
@@ -23,30 +23,17 @@ namespace MileageTraker.Web.Controllers
// default parameter processing // default parameter processing
if (!query.HasParameters()) if (!query.HasParameters())
{ {
query.Year = validLogYearMonths.First().Year; query.FiscalYear = validLogYearMonths.First().Year;
query.Month = validLogYearMonths.First().Month; query.Month = validLogYearMonths.First().Month;
} }
if (query.Year.HasValue && !query.Month.HasValue)
{
var validLogMonths = validLogYearMonths.Where(dt => dt.Year == query.Year).ToList();
query.Month = validLogMonths.Min(dt => dt.Month);
}
var fuelLogs = DataService.GetFuelLogs(); var fuelLogs = DataService.GetFuelLogs();
//fuelLogs.OrderBy(f => f.FuelLogId); var flvm = from fl in DataService.FilterFuelLogs(fuelLogs, query).ToList()
orderby fl.Date ascending
select new FuelLogIndexViewModel(fl);
//var filteredLogs = var viewModel = new FuelLogResultsViewModel(flvm, query, CustomExtensions.YearMonthList(validLogYearMonths));
// (from log in DataService.GetFuelLogIndexViewModels(DataService.FilterLogs(fuelLogs, query))
// orderby log. descending
// select log).ToList();
var flvm = from fl in fuelLogs.ToList()
select new FuelLogIndexViewModel(fl);
var viewModel = new ResultsViewModel(flvm, query, CustomExtensions.YearMonthList(validLogYearMonths));
//Session.Add("FuelLogPage", Request.Url.PathAndQuery);
return View(viewModel); return View(viewModel);
} }
@@ -173,7 +160,7 @@ namespace MileageTraker.Web.Controllers
{ {
Status = MatchStatus.NoMatch.ToString(), Status = MatchStatus.NoMatch.ToString(),
Message = "Already matched to log", Message = "Already matched to log",
Action = RenderRazorViewToString("ImportMatchLogView", fuelLog.Log.LogId) Action = RenderRazorViewToString("MatchLink", new MatchLinkViewModel(fuelLog))
}, JsonRequestBehavior.AllowGet); }, JsonRequestBehavior.AllowGet);
} }
@@ -185,7 +172,8 @@ namespace MileageTraker.Web.Controllers
return Json(new return Json(new
{ {
Status = MatchStatus.NoMatch.ToString(), Status = MatchStatus.NoMatch.ToString(),
Message = "Unable to find match" Message = "Unable to find match",
Action = RenderRazorViewToString("MatchLink", new MatchLinkViewModel(fuelLog))
}, JsonRequestBehavior.AllowGet); }, JsonRequestBehavior.AllowGet);
} }
fuelLog.Log = matchingLog; fuelLog.Log = matchingLog;
@@ -193,7 +181,7 @@ namespace MileageTraker.Web.Controllers
return Json(new return Json(new
{ {
Status = MatchStatus.Match.ToString(), Status = MatchStatus.Match.ToString(),
Action = RenderRazorViewToString("ImportMatchedLog", new ImportMatchedLogViewModel { LogId = fuelLog.Log.LogId }) Action = RenderRazorViewToString("MatchLink", new MatchLinkViewModel(fuelLog))
}, JsonRequestBehavior.AllowGet); }, JsonRequestBehavior.AllowGet);
} }
+49 -12
View File
@@ -7,6 +7,7 @@ using MileageTraker.Web.Context;
using MileageTraker.Web.Models; using MileageTraker.Web.Models;
using MileageTraker.Web.Utility; using MileageTraker.Web.Utility;
using MileageTraker.Web.ViewModels; using MileageTraker.Web.ViewModels;
using MileageTraker.Web.ViewModels.FuelLog;
using MileageTraker.Web.ViewModels.Log; using MileageTraker.Web.ViewModels.Log;
using MileageTraker.Web.ViewModels.Vehicle; using MileageTraker.Web.ViewModels.Vehicle;
@@ -658,6 +659,30 @@ namespace MileageTraker.Web.DAL
return _db.FuelLogs.Find(id); return _db.FuelLogs.Find(id);
} }
public static IQueryable<FuelLog> FilterFuelLogs(IQueryable<FuelLog> fuelLogs, FuelLogQueryViewModel query)
{
const int fiscalYearStartMonth = 7;
// date filtering
if (query.FiscalYear.HasValue & query.Month.HasValue)
{
var start = new DateTime(query.FiscalYear.Value, query.Month.Value, 1);
var end = start.AddMonths(1);
fuelLogs = fuelLogs.Where(l => l.Date >= start && l.Date < end);
}
else if (query.FiscalYear.HasValue)
{
var start = new DateTime(query.FiscalYear.Value, fiscalYearStartMonth, 1);
var end = start.AddYears(1);
fuelLogs = fuelLogs.Where(l => l.Date >= start && l.Date < end);
}
if (query.Unmatched)
{
fuelLogs = fuelLogs.Where(l => l.Log == null);
}
return fuelLogs;
}
public IEnumerable<FuelLog> GetDuplicateFuelLogs(FuelLog log) public IEnumerable<FuelLog> GetDuplicateFuelLogs(FuelLog log)
{ {
return return
@@ -671,23 +696,35 @@ namespace MileageTraker.Web.DAL
public Log GetMatchingLog(FuelLog fuelLog) public Log GetMatchingLog(FuelLog fuelLog)
{ {
var logs = GetLogs(); // add a little wiggle room for the gas - it is common to be off by a couple hundreths
const double gasRange = .01;
var gasMax = fuelLog.GasPurchased + gasRange;
var gasMin = fuelLog.GasPurchased - gasRange;
var dateGasVehicleQuery = GetLogs();
// matching by Date, Gas Purchased (gallons), and Vehicle // matching by Date, Gas Purchased (gallons), and Vehicle
var vehicle = GetVehicleByTag(fuelLog.TagNumber); var vehicle = GetVehicleByTag(fuelLog.TagNumber);
if (vehicle != null) if (vehicle != null)
logs = logs.Where(log => log.VehicleId == vehicle.VehicleId); dateGasVehicleQuery = dateGasVehicleQuery.Where(log => log.VehicleId == vehicle.VehicleId);
else return null;
logs = logs.Where(log => dateGasVehicleQuery = dateGasVehicleQuery
log.Date.Year == fuelLog.Date.Year && .Where(log =>
log.Date.Month == fuelLog.Date.Month && log.Date.Year == fuelLog.Date.Year &&
log.Date.Day == fuelLog.Date.Day log.Date.Month == fuelLog.Date.Month &&
); log.Date.Day == fuelLog.Date.Day)
logs = logs.Where(log => log.GasPurchased == fuelLog.GasPurchased); .Where(log => log.GasPurchased <= gasMax && log.GasPurchased >= gasMin);
return logs.FirstOrDefault(); var dateDriverGasQuery
= GetLogs()
.Where(log =>
log.Date.Year == fuelLog.Date.Year &&
log.Date.Month == fuelLog.Date.Month &&
log.Date.Day == fuelLog.Date.Day)
.Where(log => log.User.FullName.Contains(fuelLog.DriverFullName))
.Where(log => log.GasPurchased <= gasMax && log.GasPurchased >= gasMin);
return dateGasVehicleQuery.Union(dateDriverGasQuery).FirstOrDefault();
} }
public IEnumerable<Log> GetPossibleMatchingLogs(FuelLog fuelLog) public IEnumerable<Log> GetPossibleMatchingLogs(FuelLog fuelLog)
+10 -8
View File
@@ -1,12 +1,13 @@
function importFuelLogs() { function matchFuelLogs() {
$('.breadcrumb').hide(); $('.breadcrumb').hide();
$('tr:not(.complete) .match-status').append('<span class="label">Pending</span'); $('tr:not(.complete) .match-status').append('<span class="label">Pending</span');
var total = $("#fuellogs > tbody > tr:not(.complete)").length; var total = $("#fuellogs > tbody > tr:not(.complete)").length;
$('#page-match-status').html('<span class="label label-warning"><i class="fa fa-spinner fa-spin"></i> Matching In Progress</span> <strong>Keep page open until complete.</strong>'); $('#page-match-status').html('<span class="label label-warning"><i class="fa fa-spinner fa-spin"></i> Matching In Progress</span> for ' + total + ' fuel logs. <strong>Keep page open until complete.</strong>');
$('#page-match-status').after('<div class="progress progress-striped active"><div class="bar" style="width:0%"></div><div>'); $('#page-match-status').after('<div class="progress progress-striped active"><div class="bar" style="width:0%"></div><div>');
submitNext(); submitNext();
var failureCount = 0; var unmatchedCount = 0;
var errorCount = 0;
function submitNext() { function submitNext() {
var $fuelLogs = $("#fuellogs > tbody > tr:not(.complete)"); var $fuelLogs = $("#fuellogs > tbody > tr:not(.complete)");
@@ -32,12 +33,13 @@
else if (result.Status == "NoMatch" || result.Status == "Error") { else if (result.Status == "NoMatch" || result.Status == "Error") {
if (result.Status == "NoMatch") { if (result.Status == "NoMatch") {
$('.match-status', $row).html('<span class="label label-warning">No Match</span>'); $('.match-status', $row).html('<span class="label label-warning">No Match</span>');
unmatchedCount++;
} else { } else {
$('.match-status', $row).html('<span class="label label-important">Error</span>'); $('.match-status', $row).html('<span class="label label-important">Error</span>');
errorCount++;
$('.progress .bar').addClass('bar-warning');
} }
$('.match-message', $row).text(result.Message); $('.match-message', $row).text(result.Message);
failureCount++;
$('.progress .bar').addClass('bar-warning');
} }
if (result.Action != undefined && result.Action != null) { if (result.Action != undefined && result.Action != null) {
$('.match-status', $row).append(" " + result.Action); $('.match-status', $row).append(" " + result.Action);
@@ -50,11 +52,11 @@
$('.breadcrumb').show(); $('.breadcrumb').show();
$('.progress').removeClass('progress-striped'); $('.progress').removeClass('progress-striped');
$('.progress').removeClass('active'); $('.progress').removeClass('active');
if (failureCount == 0) { if (errorCount == 0) {
$('#page-match-status').html('<span class="label">Complete</span> with all matches.'); $('#page-match-status').html('<span class="label">Complete</span> ' + total + ' total with <strong>' + unmatchedCount + '</strong> left unmatched.');
$('.progress .bar').addClass('bar-success'); $('.progress .bar').addClass('bar-success');
} else { } else {
$('#page-match-status').html('<span class="label label-warning">Complete</span> but with errors. See details below.'); $('#page-match-status').html('<span class="label label-warning">Complete</span> with errors. See below.');
} }
} }
} }
@@ -5,27 +5,28 @@ namespace MileageTraker.Web.ViewModels.FuelLog
{ {
public class FuelLogQueryViewModel public class FuelLogQueryViewModel
{ {
public int? Year { get; set; } public int? FiscalYear { get; set; }
public int? Month { get; set; } public int? Month { get; set; }
public bool Unmatched { get; set; }
public string YearMonthStart {get public string YearMonthStart {get
{ {
return string.Format("{0}-{1:00}", Year, Month); return string.Format("{0}-{1:00}", FiscalYear, Month);
}} }}
public override string ToString() public override string ToString()
{ {
var v = new List<string>(); var v = new List<string>();
if (Year.HasValue && Month.HasValue) if (FiscalYear.HasValue && Month.HasValue)
{ {
var str = YearMonthStart; var str = YearMonthStart;
v.Add(str); v.Add(str);
} }
else if (Year.HasValue) else if (FiscalYear.HasValue)
{ {
v.Add(Year.ToString()); v.Add(FiscalYear.ToString());
} }
return String.Join("_", v).Replace(' ', '-'); return String.Join("_", v).Replace(' ', '-');
@@ -33,7 +34,7 @@ namespace MileageTraker.Web.ViewModels.FuelLog
public bool HasParameters() public bool HasParameters()
{ {
return Year.HasValue; return FiscalYear.HasValue;
} }
} }
} }
@@ -3,27 +3,29 @@ using System.Globalization;
namespace MileageTraker.Web.ViewModels.FuelLog namespace MileageTraker.Web.ViewModels.FuelLog
{ {
public class ResultsViewModel public class FuelLogResultsViewModel
{ {
public IEnumerable<FuelLogIndexViewModel> FuelLogs { get; set; } public IEnumerable<FuelLogIndexViewModel> FuelLogs { get; set; }
public Dictionary<string, List<string>> AvailableYearMonths { get; set; } public Dictionary<string, List<string>> AvailableYearMonths { get; set; }
public IEnumerable<string> SelectedYearMonths{get public IEnumerable<string> SelectedYearMonths{get
{ {
if (!string.IsNullOrEmpty(Year)) if (!string.IsNullOrEmpty(FiscalYear))
return AvailableYearMonths[Year]; return AvailableYearMonths[FiscalYear];
return new List<string>(); return new List<string>();
}} }}
// filter parameters // filter parameters
public string Year { get; set; } public string FiscalYear { get; set; }
public string Month { get; set; } public string Month { get; set; }
public bool Unmatched { get; set; }
public ResultsViewModel(IEnumerable<FuelLogIndexViewModel> fuelLogs, FuelLogQueryViewModel query, Dictionary<string, List<string>> availableYearMonths) public FuelLogResultsViewModel(IEnumerable<FuelLogIndexViewModel> fuelLogs, FuelLogQueryViewModel query, Dictionary<string, List<string>> availableYearMonths)
{ {
FuelLogs = fuelLogs; FuelLogs = fuelLogs;
AvailableYearMonths = availableYearMonths; AvailableYearMonths = availableYearMonths;
Year = query.Year.HasValue ? query.Year.Value.ToString(CultureInfo.InvariantCulture) : string.Empty; FiscalYear = query.FiscalYear.HasValue ? query.FiscalYear.Value.ToString(CultureInfo.InvariantCulture) : string.Empty;
Month = query.Month.HasValue ? query.Month.Value.ToString(CultureInfo.InvariantCulture) : string.Empty; Month = query.Month.HasValue ? query.Month.Value.ToString(CultureInfo.InvariantCulture) : string.Empty;
Unmatched = query.Unmatched;
} }
} }
} }
@@ -1,7 +0,0 @@
namespace MileageTraker.Web.ViewModels.FuelLog
{
public class ImportMatchedLogViewModel
{
public int LogId { get; set; }
}
}
@@ -0,0 +1,22 @@
namespace MileageTraker.Web.ViewModels.FuelLog
{
public class MatchLinkViewModel
{
public int FuelLogId { get; set; }
public int? LogId { get; set; }
public MatchLinkViewModel(Models.FuelLog fuelLog)
{
FuelLogId = fuelLog.FuelLogId;
if (fuelLog.Log != null)
LogId = fuelLog.Log.LogId;
}
public MatchLinkViewModel(ImportFuelLogViewModel viewModel)
{
FuelLogId = viewModel.FuelLogId;
if (viewModel.LogId != null)
LogId = viewModel.LogId;
}
}
}
+1 -5
View File
@@ -1,8 +1,4 @@
using System; using System.Collections.Generic;
using System.Collections.Generic;
using System.Linq;
using System.Web.Mvc;
using MiscUtil.Collections;
namespace MileageTraker.Web.ViewModels.Log namespace MileageTraker.Web.ViewModels.Log
{ {
+2 -2
View File
@@ -45,7 +45,7 @@
} }
@if (viewModel.LogId != null) @if (viewModel.LogId != null)
{ {
@Html.Partial("ImportMatchedLog", new ImportMatchedLogViewModel { LogId = viewModel.LogId.Value }) @Html.Partial("MatchLink", new MatchLinkViewModel (viewModel))
} }
</td> </td>
<td class="match-message"></td> <td class="match-message"></td>
@@ -65,7 +65,7 @@
<script src="@Url.Content("~/Scripts/Shared/FuelLogImport.js")" type="text/javascript"></script> <script src="@Url.Content("~/Scripts/Shared/FuelLogImport.js")" type="text/javascript"></script>
<script type="text/javascript"> <script type="text/javascript">
$(function () { $(function () {
importFuelLogs(); matchFuelLogs();
}); });
</script> </script>
} }
@@ -1,5 +0,0 @@
@model MileageTraker.Web.ViewModels.FuelLog.ImportMatchedLogViewModel
@{
Layout = null;
}
@Html.ActionLink("View Log", "Details", "Log", new{id=Model.LogId}, new{@class="btn btn-mini", target="_blank"})
+12 -11
View File
@@ -1,10 +1,10 @@
@using MileageTraker.Web.Utility @using MileageTraker.Web.Utility
@model MileageTraker.Web.ViewModels.FuelLog.ResultsViewModel @model MileageTraker.Web.ViewModels.FuelLog.FuelLogResultsViewModel
@{ @{
ViewBag.Title = "Fuel Logs"; ViewBag.Title = "Fuel Logs";
var grid = new WebGrid(Model.FuelLogs, rowsPerPage: 45); var grid = new WebGrid(Model.FuelLogs, rowsPerPage: 45);
var parameters = new {Model.Year, Model.Month}; var parameters = new {Year = Model.FiscalYear, Model.Month};
} }
@section Styles { @section Styles {
<link href="@Url.Content("~/Content/VehicleColors.css")" rel="stylesheet" type="text/css" /> <link href="@Url.Content("~/Content/VehicleColors.css")" rel="stylesheet" type="text/css" />
@@ -12,9 +12,9 @@
@section Scripts { @section Scripts {
<script type="text/javascript"> <script type="text/javascript">
var availableLogYearMonths = @Html.Raw(Json.Encode(Model.AvailableYearMonths)); var availableLogYearMonths = @Html.Raw(Json.Encode(Model.AvailableYearMonths));
@if (Model.Year != null) @if (Model.FiscalYear != null)
{ {
<text>var selectedYear = "@Model.Year";</text> <text>var selectedYear = "@Model.FiscalYear";</text>
<text>var selectedMonth = "@Model.Month";</text> <text>var selectedMonth = "@Model.Month";</text>
} }
</script> </script>
@@ -22,12 +22,10 @@
@Html.Partial("_StatusMessage") @Html.Partial("_StatusMessage")
<div class="btn-toolbar pull-right"> <div class="btn-toolbar pull-right" style="width:400px">
@using (Html.BeginForm("Index", "Log", FormMethod.Get, new { id = "filter", @class = "form" })) @using (Html.BeginForm("Index", "FuelLog", FormMethod.Get, new { id = "filter", @class = "form" }))
{ {
<div class=""> @Html.EditorForModel()
@*@Html.EditorForModel()*@
</div>
} }
</div> </div>
@@ -54,8 +52,11 @@
: Html.ActionLink("Match", "Match", new {id = item.FuelLogId}, new {@class = "btn btn-warning btn-mini", target="_blank"})) : Html.ActionLink("Match", "Match", new {id = item.FuelLogId}, new {@class = "btn btn-warning btn-mini", target="_blank"}))
</text>)), </text>)),
htmlAttributes: new { @class = "table table-striped table-bordered table-hover table-condensed"}, htmlAttributes: new { @class = "table table-striped table-bordered table-hover table-condensed"},
numericLinksCount: 20 numericLinksCount: 20
) )
<div class="center-content webgrid-footer">
Total Results: @Model.FuelLogs.Count()
</div>
</div> </div>
@if (!Model.FuelLogs.Any()) @if (!Model.FuelLogs.Any())
{ {
+12
View File
@@ -0,0 +1,12 @@
@model MileageTraker.Web.ViewModels.FuelLog.MatchLinkViewModel
@{
Layout = null;
}
@if (Model.LogId != null)
{
@Html.ActionLink("View Match", "Match", new {id = Model.FuelLogId}, new {@class = "btn btn-mini", target = "_blank"})
}
else
{
@Html.ActionLink("Match", "Match", new {id = Model.FuelLogId}, new {@class = "btn btn-warning btn-mini", target = "_blank"})
}
@@ -0,0 +1,21 @@
@using MileageTraker.Web.Models
@using MileageTraker.Web.Utility
@model MileageTraker.Web.ViewModels.FuelLog.FuelLogResultsViewModel
<div class="row-fluid">
<div class="span4">
@Html.Label("FiscalYear", "Fiscal Year")
@Html.DropDownList("FiscalYear", new SelectList(Model.AvailableYearMonths.Keys, Model.FiscalYear), "Select Year", new { @class = "input-small" })
</div>
<div class="span3">
@Html.Label("Month", "Month")
@Html.DropDownList("Month", new SelectList(Model.SelectedYearMonths, Model.Month), "All Months", new { @class = "input-small" })
</div>
<div class="span3" >
@Html.Label("Unmatched", "Unmatched Only", new{style = "padding-top:22px"})
<input id="Unmatched" name="Unmatched" type="checkbox" @(Model.Unmatched ? "checked='checked'" : "" ) value="true" />
</div>
<div class="span2">
<input type="submit" value="Filter" class="btn" style="margin-top:1.75em" />
</div>
</div>
+4 -3
View File
@@ -233,12 +233,12 @@
<Compile Include="ViewModels\FuelLog\FuelLogIndexViewModel.cs" /> <Compile Include="ViewModels\FuelLog\FuelLogIndexViewModel.cs" />
<Compile Include="ViewModels\FuelLog\FuelLogViewModel.cs" /> <Compile Include="ViewModels\FuelLog\FuelLogViewModel.cs" />
<Compile Include="ViewModels\FuelLog\ImportFuelLogViewModel.cs" /> <Compile Include="ViewModels\FuelLog\ImportFuelLogViewModel.cs" />
<Compile Include="ViewModels\FuelLog\ImportMatchedLogViewModel.cs" /> <Compile Include="ViewModels\FuelLog\MatchLinkViewModel.cs" />
<Compile Include="ViewModels\FuelLog\ImportUploadViewModel.cs" /> <Compile Include="ViewModels\FuelLog\ImportUploadViewModel.cs" />
<Compile Include="ViewModels\FuelLog\FuelLogQueryViewModel.cs" /> <Compile Include="ViewModels\FuelLog\FuelLogQueryViewModel.cs" />
<Compile Include="ViewModels\FuelLog\LogMatchViewModel.cs" /> <Compile Include="ViewModels\FuelLog\LogMatchViewModel.cs" />
<Compile Include="ViewModels\FuelLog\MatchViewModel.cs" /> <Compile Include="ViewModels\FuelLog\MatchViewModel.cs" />
<Compile Include="ViewModels\FuelLog\ResultsViewModel.cs" /> <Compile Include="ViewModels\FuelLog\FuelLogResultsViewModel.cs" />
<Compile Include="ViewModels\Log\ImportLogViewModel.cs" /> <Compile Include="ViewModels\Log\ImportLogViewModel.cs" />
<Compile Include="ViewModels\Log\ImportUploadViewModel.cs" /> <Compile Include="ViewModels\Log\ImportUploadViewModel.cs" />
<Compile Include="ViewModels\Log\LogViewModel.cs" /> <Compile Include="ViewModels\Log\LogViewModel.cs" />
@@ -307,12 +307,13 @@
<Content Include="Views\FuelLog\ImportUpload.cshtml" /> <Content Include="Views\FuelLog\ImportUpload.cshtml" />
<Content Include="Views\FuelLog\Import.cshtml" /> <Content Include="Views\FuelLog\Import.cshtml" />
<Content Include="Views\FuelLog\Index.cshtml" /> <Content Include="Views\FuelLog\Index.cshtml" />
<Content Include="Views\FuelLog\ImportMatchedLog.cshtml" /> <Content Include="Views\FuelLog\MatchLink.cshtml" />
<Content Include="Views\FuelLog\Empty.cshtml" /> <Content Include="Views\FuelLog\Empty.cshtml" />
<Content Include="Views\FuelLog\Details.cshtml" /> <Content Include="Views\FuelLog\Details.cshtml" />
<Content Include="Views\Log\DetailsPrevious.cshtml" /> <Content Include="Views\Log\DetailsPrevious.cshtml" />
<Content Include="Views\FuelLog\Match.cshtml" /> <Content Include="Views\FuelLog\Match.cshtml" />
<Content Include="Views\FuelLog\MatchLogViewModelPartial.cshtml" /> <Content Include="Views\FuelLog\MatchLogViewModelPartial.cshtml" />
<Content Include="Views\Shared\EditorTemplates\FuelLogResultsViewModel.cshtml" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Content Include="Content\Account.Login.css" /> <Content Include="Content\Account.Login.css" />