Service reminder implemented.

This commit is contained in:
2015-10-21 20:32:37 -04:00
parent e42fcd16fa
commit 93065de77f
26 changed files with 652 additions and 89 deletions
+47 -16
View File
@@ -21,22 +21,6 @@ namespace MileageTraker.Web.Controllers
return View(viewModel);
}
[HttpGet]
[RequireRequestValue("vehicleId")]
public ActionResult Create(string vehicleId)
{
var viewModel = new ServiceReminderViewModel
{
VehicleId = vehicleId,
};
var vehicle = DataService.GetVehicle(vehicleId);
if (vehicle.CurrentOdometer.HasValue)
viewModel.TargetOdometer = (int) (Math.Ceiling((decimal) ((vehicle.CurrentOdometer.Value + 3000)/1000))*1000);
return View(viewModel);
}
public ActionResult Delete(int id)
{
var serviceReminder = DataService.GetServiceReminder(id);
@@ -45,6 +29,24 @@ namespace MileageTraker.Web.Controllers
return RedirectToAction("Index", new { vehicleId });
}
[HttpGet]
[RequireRequestValue("vehicleId")]
public ActionResult Create(string vehicleId)
{
var vehicle = DataService.GetVehicle(vehicleId);
var viewModel = new ServiceReminderViewModel
{
VehicleId = vehicleId,
CurrentOdometer = vehicle.CurrentOdometer
};
if (vehicle.CurrentOdometer.HasValue)
viewModel.TargetOdometer = vehicle.CurrentOdometer.Value + 4000;
return View(viewModel);
}
[HttpPost]
[ActionLog]
public ActionResult Create(ServiceReminderViewModel viewModel)
@@ -54,6 +56,7 @@ namespace MileageTraker.Web.Controllers
var serviceReminder = viewModel.GetServiceReminder();
serviceReminder.Vehicle = DataService.GetVehicle(viewModel.VehicleId);
DataService.AddServiceReminder(serviceReminder);
SetStatusMessage(
@@ -63,5 +66,33 @@ namespace MileageTraker.Web.Controllers
return View(viewModel);
}
[HttpGet]
public ActionResult Edit(int id)
{
var serviceReminder = DataService.GetServiceReminder(id);
var viewModel = new ServiceReminderViewModel(serviceReminder);
return View(viewModel);
}
[HttpPost]
[ActionLog]
public ActionResult Edit(ServiceReminderViewModel viewModel)
{
if (ModelState.IsValid)
{
var serviceReminder = viewModel.GetServiceReminder();
serviceReminder.Vehicle = DataService.GetVehicle(viewModel.VehicleId);
DataService.UpdateServiceReminder(serviceReminder);
SetStatusMessage(
string.Format("Service Reminder at {0} miles updated", viewModel.TargetOdometer), StatusType.Success);
return RedirectToAction("Index", new {vehicleId = viewModel.VehicleId});
}
return View(viewModel);
}
}
}
+5 -1
View File
@@ -13,7 +13,11 @@ namespace MileageTraker.Web.Controllers
{
public ViewResult Index(bool inactive = false)
{
var vehicles = DataService.GetVehicles().Where(v => inactive == (v.InactiveDate.HasValue && v.InactiveDate.Value < DateTime.Today.AddDays(1)));
var vehicles =
(from v in DataService.GetVehicles().ToList()
where inactive == (v.InactiveDate.HasValue && v.InactiveDate.Value < DateTime.Today.AddDays(1))
select new VehicleViewModel(v)).ToList();
var viewModel = new VehicleResultsViewModel(vehicles, inactive);
return View(viewModel);
}
+97 -2
View File
@@ -1,9 +1,12 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web.Mvc;
using MileageTraker.Web.Attributes;
using MileageTraker.Web.DAL;
using MileageTraker.Web.Models;
using MileageTraker.Web.Utility;
using MileageTraker.Web.ViewModels;
using MileageTraker.Web.ViewModels.ServiceReminder;
using MileageTraker.Web.ViewModels.VehicleService;
namespace MileageTraker.Web.Controllers
@@ -23,8 +26,14 @@ namespace MileageTraker.Web.Controllers
var vehicleServices = DataService.FilterVehicleServices(DataService.GetVehicleServices(), query).ToList();
var upcomingServiceReminders =
DataService.GetUpcomingServiceReminders().ToList()
.Select(sr => new ServiceReminderViewModel(sr)).ToList();
upcomingServiceReminders.Sort();
var viewModel = new VehicleServiceResultsViewModel(
vehicleServices.Select(s => new VehicleServiceViewModel(s)),
upcomingServiceReminders,
query,
CustomExtensions.YearMonthList(validVehicleServiceYearMonths));
@@ -78,18 +87,104 @@ namespace MileageTraker.Web.Controllers
{
var vehicleService = viewModel.GetVehicleService();
vehicleService.Vehicle = DataService.GetVehicle(viewModel.VehicleId);
DataService.AddVehicleService(vehicleService);
SetStatusMessage(
string.Format("Vehicle Service for vehicle {0} created", viewModel.VehicleId), StatusType.Success);
return RedirectToAction("UpdateServiceReminders", new {vehicleId = viewModel.VehicleId});
}
return View(viewModel);
}
[HttpGet]
public ActionResult UpdateServiceReminders(string vehicleId)
{
var vehicle = DataService.GetVehicle(vehicleId);
var viewModel = new UpdateServiceRemindersViewModel { VehicleId = vehicleId };
RefreshServiceReminderViewModel(viewModel, vehicle, true);
if (vehicle.CurrentOdometer.HasValue)
viewModel.TargetOdometer = vehicle.CurrentOdometer.Value + 4000;
return View(viewModel);
}
[HttpPost]
[ActionLog]
public ActionResult UpdateServiceReminders(UpdateServiceRemindersViewModel viewModel)
{
var vehicle = DataService.GetVehicle(viewModel.VehicleId);
RefreshServiceReminderViewModel(viewModel, vehicle); // this data doesn't get posted back
if (ModelState.IsValid)
{
var serviceReminder = viewModel.GetServiceReminder();
var status = new List<string>();
// handle the new service reminder
if (serviceReminder != null)
{
serviceReminder.Vehicle = vehicle;
DataService.AddServiceReminder(serviceReminder);
status.Add(string.Format("Service Reminder at {0} miles created", viewModel.TargetOdometer));
}
// handle any deletion
if (viewModel.DeleteServiceReminders != null
&& viewModel.DeleteServiceReminders.Selected != null
&& viewModel.DeleteServiceReminders.Selected.Length > 0)
{
var serviceReminders =
(from sr in vehicle.ServiceReminders
select new { format = UpdateServiceRemindersViewModel.FormatServiceReminder(sr), id = sr.ServiceReminderId })
.ToDictionary(o => o.format);
foreach (var selected in viewModel.DeleteServiceReminders.Selected)
{
DataService.DeleteServiceReminder(serviceReminders[selected].id);
}
status.Add(string.Format("Selected {0} service reminders deleted", viewModel.DeleteServiceReminders.Selected.Count()));
}
if (status.Count > 0)
{
SetStatusMessage(string.Join(", ", status), StatusType.Success);
}
return RedirectToAction("Index");
}
return View(viewModel);
}
private void RefreshServiceReminderViewModel(
UpdateServiceRemindersViewModel viewModel, Vehicle vehicle, bool setDefaultSelectedServiceReminders = false)
{
if (viewModel.DeleteServiceReminders == null)
viewModel.DeleteServiceReminders = new CheckBoxViewModel();
var existingServiceReminders =
(from sr in vehicle.ServiceReminders
select UpdateServiceRemindersViewModel.FormatServiceReminder(sr)).ToArray();
viewModel.DeleteServiceReminders.Available = existingServiceReminders;
viewModel.CurrentOdometer = vehicle.CurrentOdometer;
if (vehicle.CurrentOdometer.HasValue && setDefaultSelectedServiceReminders)
{
viewModel.DeleteServiceReminders.Selected =
(from sr in vehicle.ServiceReminders
where sr.TargetOdometer <= vehicle.CurrentOdometer.Value
select UpdateServiceRemindersViewModel.FormatServiceReminder(sr)).ToArray();
}
}
public ActionResult Edit(int id)
{
var vehicleService = DataService.GetVehicleService(id);
+23 -1
View File
@@ -407,7 +407,7 @@ namespace MileageTraker.Web.DAL
_db.SaveChanges();
}
public IEnumerable<Vehicle> GetVehicles()
public IQueryable<Vehicle> GetVehicles()
{
var vehicles = _db.Vehicles;
return vehicles;
@@ -901,6 +901,17 @@ namespace MileageTraker.Web.DAL
return _db.ServiceReminders.Find(id);
}
public ServiceReminder FindDuplicateServiceReminder(string vehicleId, int targetOdometer, string description)
{
return
(from sr in _db.ServiceReminders
where
sr.Vehicle.VehicleId == vehicleId &&
sr.TargetOdometer == targetOdometer &&
sr.Description == description
select sr).FirstOrDefault();
}
public void DeleteServiceReminder(int id)
{
var serviceReminder = _db.ServiceReminders.Find(id);
@@ -909,6 +920,17 @@ namespace MileageTraker.Web.DAL
_db.SaveChanges();
}
public IQueryable<ServiceReminder> GetUpcomingServiceReminders()
{
const int mileageThreshold = 500;
return
from sr in _db.ServiceReminders
where
sr.Vehicle.CurrentOdometer.HasValue
&& sr.Vehicle.CurrentOdometer.Value > (sr.TargetOdometer - mileageThreshold)
select sr;
}
#endregion
}
}
+1
View File
@@ -12,6 +12,7 @@ namespace MileageTraker.Web.Models
public virtual Vehicle Vehicle { get; set; }
[InputSize("small")]
[Range(1, 500000, ErrorMessage = "Between 1 and 500k")]
public int TargetOdometer { get; set; }
[StringLength(64)]
+5 -2
View File
@@ -5,6 +5,8 @@
$('#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>');
$(".match-status a").addClass('pull-right');
submitNext();
var unmatchedCount = 0;
var errorCount = 0;
@@ -41,10 +43,11 @@
}
$('.match-message', $row).text(result.Message);
}
if (result.Action != undefined && result.Action != null) {
$('.match-status', $row).append(" " + result.Action);
var $action = $("a[matchcount]", $row);
matchCountFunc.apply($action);
$("a", $row).addClass('pull-right');
matchCountFunc.apply($("a[matchcount]", $row));
}
submitNext();
+77 -10
View File
@@ -75,7 +75,7 @@ $(function () {
var $recentLogs = $("#RecentLogs");
if ($recentLogs.length > 0) {
$.ajax({
url: "/FuelLog/RecentLogs",
url: "/CreateLog/RecentLogs",
success: function (data) {
$recentLogs.append(data);
}
@@ -83,12 +83,69 @@ $(function () {
}
});
/**
Ability to run the defered promises in sequence
*/
(function ($) {
"use strict";
var copy = function (a) {
return Array.prototype.slice.call(a);
};
/**
Handle a sequence of methods, stopping on failure by default
@param Array<Function> chain List of methods to execute. Non-deferred return values will be treated as successful deferreds.
@param Boolean continueOnFailure Continue executing even if one of the returned deferreds fails.
@returns Deferred
*/
$.sequence = function (chain, continueOnFailure) {
var handleStep, handleResult,
steps = copy(chain),
def = new $.Deferred(),
defs = [],
results = [];
handleStep = function () {
if (!steps.length) {
def.resolveWith(defs, [results]);
return;
}
var step = steps.shift(),
result = step; // used to be step();, but we're already dealing with promises
handleResult(
$.when(result).always(function () {
defs.push(this);
}).done(function () {
results.push({ resolved: copy(arguments) });
}).fail(function () {
results.push({ rejected: copy(arguments) });
})
);
};
handleResult = continueOnFailure ?
function (result) {
result.always(function () {
handleStep();
});
} :
function (result) {
result.done(handleStep)
.fail(function () {
def.rejectWith(defs, [results]);
});
};
handleStep();
return def.promise();
};
}(this.jQuery));
var matchCountFunc = function() {
var $link = $(this);
var url = $link.attr("matchcount");
$link.append(' <span class="badge"><i class="fa fa-spinner fa-spin"></i></span>');
return $.ajax({
url: url,
success: function(matchcount) {
success: function (matchcount) {
$('span', $link).remove();
if (matchcount > 0) {
$link.append(" <span class='badge badge-info'>" + matchcount + "</span>");
} else {
@@ -101,7 +158,7 @@ var matchCountFunc = function() {
// add get match count for all the current items
$(function () {
var $requests = $("a[matchcount]").map(matchCountFunc);
$.when.apply($, $requests);
$.sequence($requests);
});
$(function() {
@@ -124,7 +181,7 @@ $(function() {
var idNavActiveRegex = {
'fuellog-nav': /\/fuellog/i,
'log-nav': /\/log/i,
'vehicle-nav': /\/vehicle/i,
'vehicle-nav': /\/vehicle|\/servicereminder/i,
'user-nav': /\/user/i,
'config-nav': /\/City|\/Purpose/i
};
@@ -142,23 +199,33 @@ $(function() {
function addButtonIcons () {
var textToIcon = {
'Enter Log': 'plus',
'Edit': 'edit',
'Filter': 'filter',
'Details' : 'zoom-in',
'Details': 'info-circle',
'Delete': 'trash',
'Add': 'plus',
'Export': 'download',
'Import': 'upload',
'Driver Mileage': 'user',
'Vehicle Mileage': 'car',
'Show Active': 'ok-circle',
'Show Inactive': 'ban-circle',
'Set Inactive' : 'ban-circle',
'Reactivate' : 'ok-circle'
'Show Active': 'check-circle',
'Show Inactive': 'ban',
'Set Inactive' : 'ban',
'Reactivate': 'check-circle',
'Config': 'cog',
'Cities': 'map',
'Purposes': 'arrow-right',
'Vehicle Service': 'wrench',
'Vehicle': 'car',
'Reminder': 'clock-o',
'Fuel Logs': 'tachometer',
'Users': 'user',
'Logs': 'road'
};
$.each(textToIcon, function(text, icon) {
$("a:contains('" + text + "'):not(:has(i))")
.prepend('<i class="icon-' + icon + '" /> ');
.prepend('<i class="fa fa-'+ icon +'"></i> ');
});
$(".navbar-inverse a[title='Manage']:not(:has(i))")
@@ -1,11 +1,14 @@
using System.ComponentModel.DataAnnotations;
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Web.Mvc;
using AutoMapper;
using MileageTraker.Web.Attributes;
using MileageTraker.Web.DAL;
namespace MileageTraker.Web.ViewModels.ServiceReminder
{
public class ServiceReminderViewModel
public class ServiceReminderViewModel : IComparable<ServiceReminderViewModel>, IValidatableObject
{
[HiddenInput(DisplayValue = false)]
public int? ServiceReminderId { get; set; }
@@ -19,17 +22,28 @@ namespace MileageTraker.Web.ViewModels.ServiceReminder
[InputSize("mini")]
public string VehicleId { get; set; }
[HiddenInput(DisplayValue = false)]
public int? CurrentOdometer { get; set; }
[InputSize("small")]
[Range(1, 500000, ErrorMessage = "Between 1 and 500k")]
public int TargetOdometer { get; set; }
[StringLength(64)]
public string Description { get; set; }
[HiddenInput(DisplayValue = false)]
public bool IsServiceOverdue { get { return ServiceDueInMiles <= 0; } }
[HiddenInput(DisplayValue = false)]
public int ServiceDueInMiles { get { return CurrentOdometer.HasValue ? TargetOdometer - CurrentOdometer.Value : int.MaxValue; } }
static ServiceReminderViewModel()
{
Mapper.CreateMap<ServiceReminderViewModel, Models.ServiceReminder>();
Mapper.CreateMap<Models.ServiceReminder, ServiceReminderViewModel>()
.ForMember(dest => dest.VehicleId, opt => opt.MapFrom(src => src.Vehicle.VehicleId));
.ForMember(dest => dest.VehicleId, opt => opt.MapFrom(src => src.Vehicle.VehicleId))
.ForMember(dest => dest.CurrentOdometer, opt => opt.MapFrom(src => src.Vehicle.CurrentOdometer));
}
public ServiceReminderViewModel(Models.ServiceReminder serviceReminder)
@@ -47,5 +61,32 @@ namespace MileageTraker.Web.ViewModels.ServiceReminder
Mapper.Map(this, serviceReminder);
return serviceReminder;
}
public int CompareTo(ServiceReminderViewModel other)
{
return ServiceDueInMiles - other.ServiceDueInMiles;
}
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
using (var dataService = new DataService())
{
var duplicateServiceReminder = dataService.FindDuplicateServiceReminder(VehicleId, TargetOdometer, Description);
if (duplicateServiceReminder != null)
yield return new ValidationResult("Exact duplicate of this reminder already exists");
var vehicle = dataService.GetVehicle(VehicleId);
if (vehicle.InactiveDate.HasValue)
yield return new ValidationResult("Vehicle is inactive, no service reminder permitted");
// no reminders for mileage less than the current odometer
if (vehicle.CurrentOdometer.HasValue && TargetOdometer <= vehicle.CurrentOdometer)
yield return new ValidationResult(
string.Format("Target odometer {0} must be greater than current odometer {1}",
TargetOdometer, vehicle.CurrentOdometer.Value), new[]{"TargetOdometer"});
}
}
}
}
@@ -4,10 +4,10 @@ namespace MileageTraker.Web.ViewModels.Vehicle
{
public class VehicleResultsViewModel
{
public IEnumerable<Models.Vehicle> Vehicles { get; set; }
public IEnumerable<VehicleViewModel> Vehicles { get; set; }
public bool Inactive { get; set; }
public VehicleResultsViewModel(IEnumerable<Models.Vehicle> vehicles, bool activeInactive)
public VehicleResultsViewModel(IEnumerable<VehicleViewModel> vehicles, bool activeInactive)
{
Vehicles = vehicles;
Inactive = activeInactive;
+142
View File
@@ -0,0 +1,142 @@
using System;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Web.Mvc;
using AutoMapper;
using MileageTraker.Web.Attributes;
namespace MileageTraker.Web.ViewModels.Vehicle
{
public class VehicleViewModel
{
[Required]
[InputSize("mini")]
public int Key { get; set; }
[Key]
[Required]
[StringLength(6, MinimumLength = 4, ErrorMessage = "Must be at least a 4 digit number")]
[Display(Name = "EHTRA ID")]
[RegularExpression(@"\d+", ErrorMessage = "Vehicle ID must be all numbers")]
[InputSize("mini")]
public string VehicleId { get; set; }
[Required]
[RegularExpression(@"\d{4}", ErrorMessage = "Must be 4 numbers")]
[InputSize("mini")]
public string ModelYear { get; set; }
[Required]
[InputSize("medium")]
public string Make { get; set; }
[Display(Name = "Model")]
[Required]
[InputSize("medium")]
public string CarModel { get; set; }
[InputSize("small")]
public string Color { get; set; }
[Required]
[RegularExpression(@"Car|Truck|SUV|Van", ErrorMessage = "Must be Car, Truck, SUV, or Van")]
[FormatHint("Car, Truck, SUV or Van")]
[InputSize("mini")]
public string Type { get; set; }
[Required]
[Display(Name = "VIN")]
[RegularExpression(@"[0-9A-HJ-NPR-Z]{17}", ErrorMessage = "VIN must be 17-characters, not including letters I, O or Q")]
[InputSize("large")]
public string Vin { get; set; }
[Required]
[InputSize("small")]
[Currency]
public decimal Price { get; set; }
[Required]
[RegularExpression(@"\d{1,2}/\d{2}", ErrorMessage = "PurDate must be in mm/yy format")]
[InputSize("small")]
[FormatHint("mm/yy")]
public string PurDate { get; set; }
[DataType(DataType.DateTime)]
[DisplayFormat(NullDisplayText = "Currently Active", DataFormatString = @"{0:MM/dd/yyyy}", ApplyFormatInEditMode = true)]
[InputSize("small")]
[FormatHint("mm/dd/yyyy")]
public DateTime? InactiveDate { get; set; }
[Required]
[Display(Name = "Tag#")]
[InputSize("small")]
public string TagNumber { get; set; }
[Required]
[InputSize("medium")]
public string Prog { get; set; }
[RegularExpression(@"Unassigned|[A-Za-z().]+(\s+[A-Za-z().]+)+", ErrorMessage = "Please enter the full name")]
[DisplayFormat(NullDisplayText = "Unassigned")]
[FormatHint("Blank for Unassigned")]
public string Assigned { get; set; }
[InputSize("medium")]
public string Notes { get; set; }
[Display(Name = "Current Odometer", ShortName= "ODO")]
[DisplayFormat(NullDisplayText = "?")]
[InputSize("small")]
public int? CurrentOdometer { get; set; }
[HiddenInput(DisplayValue = true)]
public int? NextServiceOdometer { get; set; }
[HiddenInput(DisplayValue = false)]
public bool IsNextServiceOverdue
{
get { return NextServiceDueInMiles <= 0; }
}
[HiddenInput(DisplayValue = false)]
public int NextServiceDueInMiles
{
get
{
return CurrentOdometer.HasValue && NextServiceOdometer.HasValue
? NextServiceOdometer.Value - CurrentOdometer.Value
: int.MaxValue;
}
}
static VehicleViewModel()
{
Mapper.CreateMap<VehicleViewModel, Models.Vehicle>();
Mapper.CreateMap<Models.Vehicle, VehicleViewModel>()
.ForMember(dest => dest.NextServiceOdometer,
opt => opt.ResolveUsing(v =>
v.ServiceReminders
.OrderBy(s => s.TargetOdometer)
.Select(s => (int?) s.TargetOdometer)
.FirstOrDefault()
));
}
public VehicleViewModel(Models.Vehicle vehicle)
{
Mapper.Map(vehicle, this);
}
public VehicleViewModel()
{
}
public Models.Vehicle GetVehicle()
{
var vehicle = new Models.Vehicle();
Mapper.Map(this, vehicle);
return vehicle;
}
}
}
@@ -0,0 +1,84 @@
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Web.Mvc;
using AutoMapper;
using MileageTraker.Web.Attributes;
using MileageTraker.Web.DAL;
using MileageTraker.Web.ViewModels.ServiceReminder;
namespace MileageTraker.Web.ViewModels.VehicleService
{
public class UpdateServiceRemindersViewModel : IValidatableObject
{
[Display(Name = "Existing Reminders")]
public CheckBoxViewModel DeleteServiceReminders { get; set; }
[HiddenInput(DisplayValue = false)]
public int? ServiceReminderId { get; set; }
[HiddenInput(DisplayValue = false)]
[Required]
[Remote("Exists", "Vehicle", ErrorMessage = "ID not found")]
[StringLength(6, MinimumLength = 4, ErrorMessage = "Must be at least a 4 digit number")]
[Display(Name = "Vehicle ID")]
[RegularExpression(@"\d+", ErrorMessage = "Vehicle ID must be all numbers")]
[InputSize("mini")]
public string VehicleId { get; set; }
[HiddenInput(DisplayValue = false)]
public int? CurrentOdometer { get; set; }
[InputSize("small")]
[Range(1, 500000, ErrorMessage = "Between 1 and 500k")]
public int? TargetOdometer { get; set; }
[StringLength(64)]
public string Description { get; set; }
static UpdateServiceRemindersViewModel()
{
Mapper.CreateMap<UpdateServiceRemindersViewModel, Models.ServiceReminder>();
}
public Models.ServiceReminder GetServiceReminder()
{
if (!TargetOdometer.HasValue || TargetOdometer.Value == 0)
return null;
var serviceReminder = new Models.ServiceReminder();
Mapper.Map(this, serviceReminder);
return serviceReminder;
}
public static string FormatServiceReminder(Models.ServiceReminder serviceReminder)
{
var s = "Target ODO: " + serviceReminder.TargetOdometer;
if (!string.IsNullOrEmpty(serviceReminder.Description))
s += " Description: " + serviceReminder.Description;
return s;
}
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
if (!TargetOdometer.HasValue) yield break;
using (var dataService = new DataService())
{
var duplicateServiceReminder = dataService.FindDuplicateServiceReminder(VehicleId, TargetOdometer.Value, Description);
if (duplicateServiceReminder != null)
yield return new ValidationResult("Exact duplicate of this reminder already exists");
var vehicle = dataService.GetVehicle(VehicleId);
if (vehicle.InactiveDate.HasValue)
yield return new ValidationResult("Vehicle is inactive, no service reminder permitted");
// no reminders for mileage less than the current odometer
if (vehicle.CurrentOdometer.HasValue && TargetOdometer <= vehicle.CurrentOdometer)
yield return new ValidationResult(
string.Format("Target odometer {0} must be greater than current odometer {1}",
TargetOdometer, vehicle.CurrentOdometer.Value), new[] {"TargetOdometer"});
}
}
}
}
@@ -1,11 +1,13 @@
using System.Collections.Generic;
using System.Globalization;
using MileageTraker.Web.ViewModels.ServiceReminder;
namespace MileageTraker.Web.ViewModels.VehicleService
{
public class VehicleServiceResultsViewModel
{
public IEnumerable<VehicleServiceViewModel> ServiceItems { get; set; }
public IList<ServiceReminderViewModel> UpcomingServiceReminders { get; set; }
public Dictionary<string, List<string>> AvailableYearMonths { get; set; }
public IEnumerable<string> SelectedYearMonths
{
@@ -24,10 +26,12 @@ namespace MileageTraker.Web.ViewModels.VehicleService
public VehicleServiceResultsViewModel(
IEnumerable<VehicleServiceViewModel> serviceItems,
IList<ServiceReminderViewModel> upcomingServiceReminders,
VehicleServiceQueryViewModel query,
Dictionary<string, List<string>> availableYearMonths)
{
ServiceItems = serviceItems;
UpcomingServiceReminders = upcomingServiceReminders;
AvailableYearMonths = availableYearMonths;
Year = query.Year.HasValue ? query.Year.Value.ToString(CultureInfo.InvariantCulture) : string.Empty;
Month = query.Month.HasValue ? query.Month.Value.ToString(CultureInfo.InvariantCulture) : string.Empty;
@@ -21,8 +21,10 @@ namespace MileageTraker.Web.ViewModels.VehicleService
[DataType(DataType.DateTime)]
[DisplayFormatAttribute(ApplyFormatInEditMode = true, DataFormatString = "{0:d}")]
[DenyFutureDate(ErrorMessage = "Future date")]
[FormatHint("mm/dd/yyyy")]
[InputSize("small")]
[Required]
public DateTime? InvoiceDate { get; set; }
[Required]
+4 -1
View File
@@ -1,9 +1,11 @@
@model MileageTraker.Web.ViewModels.ServiceReminder.ServiceReminderViewModel
@{
ViewBag.Title = "Create Vehicle Service Reminder";
ViewBag.Title = "Create Service Reminder";
}
@Html.Partial("_StatusMessage")
<h2 class="center-content"><i class="fa fa-clock-o"></i> @ViewBag.Title</h2>
@using (Html.BeginForm("Create", "ServiceReminder", FormMethod.Post, new { @class = "form-horizontal well center-content" }))
@@ -11,6 +13,7 @@
@Html.Partial("_ValidationSummary")
<fieldset>
<legend></legend>
@Html.DisplayFor(m => m.CurrentOdometer)
@Html.EditorForModel()
<div class="form-actions">
<input type="submit" value="Create" class="btn btn-primary" />
+1 -1
View File
@@ -1,7 +1,7 @@
@model MileageTraker.Web.ViewModels.ServiceReminder.ServiceReminderViewModel
@{
ViewBag.Title = "Vehicle Service Reminder Details" ;
ViewBag.Title = "Service Reminder Details" ;
}
@Html.Partial("_StatusMessage")
+3 -2
View File
@@ -1,18 +1,19 @@
@model MileageTraker.Web.ViewModels.ServiceReminder.ServiceReminderViewModel
@{
ViewBag.Title = "Edit Vehicle Service Reminder";
ViewBag.Title = "Edit Service Reminder";
}
@Html.Partial("_StatusMessage")
<h2 class="center-content"><i class="fa fa-clock-o"></i> @ViewBag.Title</h2>
@using (Html.BeginForm("Edit", "ServiceREminder", FormMethod.Post, new { @class = "form-horizontal well center-content" }))
@using (Html.BeginForm("Edit", "ServiceReminder", FormMethod.Post, new { @class = "form-horizontal well center-content" }))
{
@Html.Partial("_ValidationSummary")
<fieldset>
<legend></legend>
@Html.DisplayFor(m => m.CurrentOdometer)
@Html.EditorForModel()
<div class="form-actions">
<input type="submit" value="Save" class="btn btn-primary" />
+5 -3
View File
@@ -1,12 +1,13 @@
@model MileageTraker.Web.ViewModels.ServiceReminder.ServiceReminderResultsViewModel
@{
ViewBag.Title = "Vehicle Service Reminders for Vehicle Id " + Model.VehicleId;
ViewBag.Title = "Service Reminders";
}
@Html.Partial("_StatusMessage")
<h2 id="vehicle-title"><i class="fa fa-clock-o"></i> @ViewBag.Title</h2>
<h2 class="center-content"><i class="fa fa-clock-o"></i> @ViewBag.Title</h2>
<h4 class="center-content">for Vehicle @Model.VehicleId</h4>
<div class="center-content">
@foreach (var item in Model.ServiceReminderItems)
@@ -16,6 +17,7 @@
</div>
<div class="btn-toolbar center-content well">
@Html.ActionLink("Add Service Reminder", "Create", new { vehicleId = Model.VehicleId }, new{@class="btn"})
@Html.ActionLink("Add Reminder", "Create", new { vehicleId = Model.VehicleId }, new{@class="btn"})
@Html.ActionLink("Add Service", "Create", "VehicleService", new { vehicleId = Model.VehicleId }, new { @class = "btn" })
@Html.ActionLink("Vehicle Details", "Details", "Vehicle", new { id = Model.VehicleId }, new{@class="btn"})
</div>
@@ -1,7 +1,7 @@
@model MileageTraker.Web.ViewModels.ServiceReminder.ServiceReminderViewModel
<div class="well">
<h3>Service Reminder</h3>
<h4 class="center-content"><i class="fa fa-clock-o"></i> Service Reminder</h4>
@Html.DisplayForModel()
<div class='btn-toolbar'>
@Html.ActionLink("Edit", "Edit", new { id = Model.ServiceReminderId }, new { @class = "btn" })
+1 -1
View File
@@ -1,5 +1,5 @@
@if (Session["LogPage"] != null) {
<ul class="no-print breadcrumb">
<li><a href="@Session["LogPage"]">&larr; Back To Logs</a></li>
<li><a href="@Session["LogPage"]"><i class="fa fa-arrow-circle-left"></i> Back To Logs</a></li>
</ul>
}
+1 -1
View File
@@ -1,5 +1,5 @@
<ul class="no-print breadcrumb">
<li>
<a href="@Url.Action("Index", "User")">&larr; Back to Users</a>
<a href="@Url.Action("Index", "User")"><i class="fa fa-arrow-circle-left"></i> Back to Users</a>
</li>
</ul>
+1 -1
View File
@@ -1,5 +1,5 @@
<ul class="no-print breadcrumb">
<li>
<a href="@Url.Action("Index", "Vehicle")">&larr; Back to Vehicles</a>
<a href="@Url.Action("Index", "Vehicle")"><i class="fa fa-arrow-circle-left"></i> Back to Vehicles</a>
</li>
</ul>
+2 -2
View File
@@ -34,10 +34,10 @@
</a>
<ul class="dropdown-menu">
<li>
@Html.ActionLink("View Service Reminders", "Index", "ServiceReminder", new { VehicleId = Model.VehicleId }, null)
@Html.ActionLink("View Reminders", "Index", "ServiceReminder", new { VehicleId = Model.VehicleId }, null)
</li>
<li>
@Html.ActionLink("Add Completed Service", "Create", "VehicleService", new { VehicleId = Model.VehicleId }, null)
@Html.ActionLink("Add Service", "Create", "VehicleService", new { VehicleId = Model.VehicleId }, null)
</li>
</ul>
</div>
+14 -16
View File
@@ -13,7 +13,7 @@
<h2 id="vehicle-title"><i class="fa fa-car"></i> @ViewBag.Title</h2>
<div class="btn-toolbar">
@Html.ActionLink("Add Another Vehicle", "Create", null, new{@class="btn"})
@Html.ActionLink("Add Vehicle", "Create", null, new{@class="btn"})
@Html.ActionLink("Export All", "Export", null, new { @class = "btn" })
@Html.ActionLink(Model.Inactive ? "Show Active" : "Show Inactive", "Index", new {inactive = !Model.Inactive}, new { @class = "btn" })
</div>
@@ -26,13 +26,7 @@
Ethra Id
</th>
<th>
Model Yr
</th>
<th>
Make
</th>
<th>
Model
Year, Make, Model
</th>
<th>
Type
@@ -49,6 +43,9 @@
<th>
ODO
</th>
<th>
Next Service
</th>
<th></th>
</tr>
@@ -61,13 +58,7 @@
@Html.DisplayTextFor(m => item.VehicleId)
</td>
<td>
@Html.DisplayTextFor(m => item.ModelYear)
</td>
<td>
@Html.DisplayTextFor(m => item.Make)
</td>
<td>
@Html.DisplayTextFor(m => item.CarModel)
@Html.DisplayTextFor(m => item.ModelYear) @Html.DisplayTextFor(m => item.Make) @Html.DisplayTextFor(m => item.CarModel)
</td>
<td>
<span class="label @item.Color">
@@ -88,10 +79,17 @@
<td>
@Html.DisplayTextFor(m => item.CurrentOdometer)
</td>
<td>
@Html.DisplayTextFor(m => item.NextServiceOdometer)
@if (item.IsNextServiceOverdue)
{
<span class="badge badge-warning" title="overdue @(-item.NextServiceDueInMiles) miles">!</span>
}
@Html.ActionLink("Reminder", "Index", "ServiceReminder", new { vehicleId = item.VehicleId }, new { @class = "btn btn-mini pull-right" })
</td>
<td>
<div class='btn-group'>
@Html.ActionLink("Details", "Details", new { id = item.VehicleId }, new { @class = "btn btn-mini" })
@Html.ActionLink("Svc Reminder", "Index", "ServiceReminder", new { vehicleId = item.VehicleId }, new { @class = "btn btn-mini" })
</div>
</td>
</tr>
+30 -2
View File
@@ -26,11 +26,39 @@
}
</div>
<div class="pull-right" style="width:400px">
<h4>Upcoming Services</h4>
<table class="table table-striped table-bordered table-hover table-condensed">
<tr>
<th>Vehicle ID</th>
<th>ODO</th>
<th>Next Service</th>
<th>Description</th>
<th></th>
</tr>
@foreach (var sr in Model.UpcomingServiceReminders)
{
<tr>
<td>@Html.DisplayTextFor(m => sr.VehicleId)</td>
<td>@Html.DisplayTextFor(m => sr.CurrentOdometer)</td>
<td>@Html.DisplayTextFor(m => sr.TargetOdometer)
@if (sr.IsServiceOverdue)
{
<span class="badge badge-warning" title="overdue @(-sr.ServiceDueInMiles) miles">!</span>
}
</td>
<td>@Html.DisplayTextFor(m => sr.Description)</td>
<td>@Html.ActionLink("Add Service", "Create", "VehicleService", new { vehicleId = sr.VehicleId }, new { @class = "btn btn-mini" })</td>
</tr>
}
</table>
</div>
<h2 id="vehicle-title"><i class="fa fa-wrench"></i> @ViewBag.Title</h2>
<div class="btn-toolbar pull-left">
@Html.ActionLink("Add Service", "Create", null, new{@class="btn"})
@Html.ActionLink("Export", "Export", queryParams, new{@class="btn"})
@Html.ActionLink("Add Service", "Create", null, new { @class = "btn" })
@Html.ActionLink("Export", "Export", queryParams, new { @class = "btn" })
</div>
@grid.GetHtml(columns:
@@ -0,0 +1,32 @@
@model MileageTraker.Web.ViewModels.VehicleService.UpdateServiceRemindersViewModel
@{
ViewBag.Title = "Update Service Reminders";
}
@Html.Partial("_StatusMessage")
<h2 class="center-content"><i class="fa fa-clock-o"></i> @ViewBag.Title</h2>
<h5 class="center-content">Add the next service for this vehicle and remove existing reminders.</h5>
@using (Html.BeginForm("UpdateServiceReminders", "VehicleService", FormMethod.Post, new { @class = "form-horizontal well center-content" }))
{
@Html.Partial("_ValidationSummary")
@Html.HiddenFor(m => m.VehicleId)
if (Model.DeleteServiceReminders.Available.Count > 0)
{
<fieldset>
<legend>Delete Reminders for this Vehicle?</legend>
@Html.EditorFor(m => m.DeleteServiceReminders)
</fieldset>
}
<fieldset>
<legend>Add a new Reminder?</legend>
@Html.DisplayFor(m => m.CurrentOdometer)
@Html.EditorFor(m => m.TargetOdometer)
@Html.EditorFor(m => m.Description)
</fieldset>
<div class="form-actions">
<input type="submit" value="Update" class="btn btn-primary" />
</div>
}
+3
View File
@@ -146,6 +146,7 @@
<Compile Include="Controllers\AccountController.cs" />
<Compile Include="Controllers\CityController.cs" />
<Compile Include="Controllers\ControllerBase.cs" />
<Compile Include="ViewModels\VehicleService\UpdateServiceRemindersViewModel.cs" />
<Compile Include="Controllers\FuelLogController.cs" />
<Compile Include="Controllers\ServiceReminderController.cs" />
<Compile Include="Controllers\VehicleServiceController.cs" />
@@ -268,6 +269,7 @@
<Compile Include="ViewModels\VehicleService\VehicleServiceQueryViewModel.cs" />
<Compile Include="ViewModels\VehicleService\VehicleServiceResultsViewModel.cs" />
<Compile Include="ViewModels\VehicleService\VehicleServiceViewModel.cs" />
<Compile Include="ViewModels\Vehicle\VehicleViewModel.cs" />
<Compile Include="ViewModels\Vehicle\VehicleResultsViewModel.cs" />
<Compile Include="ViewModels\SelectListViewModel.cs" />
<Compile Include="ViewModels\User\ExportUserViewModel.cs" />
@@ -350,6 +352,7 @@
<Content Include="Views\ServiceReminder\Edit.cshtml" />
<Content Include="Views\ServiceReminder\Index.cshtml" />
<Content Include="Views\ServiceReminder\ServiceReminderViewModel.cshtml" />
<Content Include="Views\VehicleService\UpdateServiceReminders.cshtml" />
</ItemGroup>
<ItemGroup>
<Content Include="Content\Account.Login.css" />