From 052f812d6f20f96ea99545937e18a55dc6528820 Mon Sep 17 00:00:00 2001 From: James Kolpack Date: Wed, 14 Sep 2016 07:53:41 -0400 Subject: [PATCH] Remove inventory, delete transactions --- InventoryTraker.Web/App_Start/SeedData.cs | 6 +- .../Attributes/ActionLogAttribute.cs | 27 + .../Controllers/ControllerBase.cs | 11 +- .../Controllers/InventoryController.cs | 87 +- .../Controllers/TransactionController.cs | 37 +- InventoryTraker.Web/Core/Transaction.cs | 8 - InventoryTraker.Web/Core/TransactionType.cs | 10 + .../InventoryTraker.Web.csproj | 23 +- .../Models/InventoryRemoveForm.cs | 23 + InventoryTraker.Web/NLog.config | 22 + InventoryTraker.Web/NLog.xsd | 2655 +++++++++++++++++ .../Views/Profile/Index.cshtml | 2 +- InventoryTraker.Web/css/layout.css | 6 +- .../js/inventory/InventoryAddDirective.js | 17 +- .../inventory/InventoryDistributeDirective.js | 5 +- .../js/inventory/InventoryEditDirective.js | 46 +- .../js/inventory/InventoryListDirective.js | 8 + .../js/inventory/inventoryInfoDirective.js | 20 + .../js/inventory/inventoryRemoveDirective.js | 45 + .../js/inventory/inventorySvc.js | 38 +- .../templates/inventoryAdd.tmpl.cshtml | 15 +- .../templates/inventoryDistribute.tmpl.cshtml | 15 +- .../templates/inventoryEdit.tmpl.cshtml | 103 +- .../templates/inventoryInfo.tmpl.cshtml | 19 + .../templates/inventoryList.tmpl.cshtml | 6 +- .../templates/inventoryRemove.tmpl.cshtml | 62 + .../InventoryTypeAddDirective.js | 5 +- .../InventoryTypeEditDirective.js | 4 +- .../templates/inventoryTypeAdd.tmpl.cshtml | 15 +- .../templates/inventoryTypeEdit.tmpl.cshtml | 17 +- .../js/transaction/TransactionController.js | 13 +- .../js/transaction/transactionSvc.js | 16 +- InventoryTraker.Web/packages.config | 6 +- 33 files changed, 3250 insertions(+), 142 deletions(-) create mode 100644 InventoryTraker.Web/Attributes/ActionLogAttribute.cs create mode 100644 InventoryTraker.Web/Core/TransactionType.cs create mode 100644 InventoryTraker.Web/Models/InventoryRemoveForm.cs create mode 100644 InventoryTraker.Web/NLog.config create mode 100644 InventoryTraker.Web/NLog.xsd create mode 100644 InventoryTraker.Web/js/inventory/inventoryInfoDirective.js create mode 100644 InventoryTraker.Web/js/inventory/inventoryRemoveDirective.js create mode 100644 InventoryTraker.Web/js/inventory/templates/inventoryInfo.tmpl.cshtml create mode 100644 InventoryTraker.Web/js/inventory/templates/inventoryRemove.tmpl.cshtml diff --git a/InventoryTraker.Web/App_Start/SeedData.cs b/InventoryTraker.Web/App_Start/SeedData.cs index 7deb09d..ec5a185 100644 --- a/InventoryTraker.Web/App_Start/SeedData.cs +++ b/InventoryTraker.Web/App_Start/SeedData.cs @@ -75,13 +75,17 @@ namespace InventoryTraker.Web var r = new Random(1); var inventoryTypes = context.InventoryTypes.ToList(); var cityNames = new List {"Maryville", "Wartburg", "Clinton", "Oak Ridge", "Alcoa", "Jellico"}; + var colorNames = new List {"Red", "Purple", "Blue", "Yellow", "White"}; for (int i = 0; i < 200; i++) { var inventoryType = inventoryTypes.ElementAt(r.Next(0, context.InventoryTypes.Count())); var addedDate = DateTime.Today.AddMonths(-r.Next(1, 24)); var expiration = addedDate.AddMonths(r.Next(2, 48)); - var memo = "New " + inventoryType.Name; + var memo = + r.Next(0,3) > 0 + ? colorNames.ElementAt(r.Next(0, colorNames.Count)) + $" {inventoryType.ContainerType}" + : string.Empty; var quantity = r.Next(5, 112); var previousTransaction = new Transaction diff --git a/InventoryTraker.Web/Attributes/ActionLogAttribute.cs b/InventoryTraker.Web/Attributes/ActionLogAttribute.cs new file mode 100644 index 0000000..4493902 --- /dev/null +++ b/InventoryTraker.Web/Attributes/ActionLogAttribute.cs @@ -0,0 +1,27 @@ +using System.Linq; +using System.Web.Mvc; +using NLog; + +namespace InventoryTraker.Web.Attributes +{ + public class ActionLogAttribute : ActionFilterAttribute + { + public override void OnActionExecuting(ActionExecutingContext filterContext) + { + if (filterContext != null) + { + var controller = filterContext.RouteData.Values["controller"].ToString(); + var action = filterContext.RouteData.Values["action"].ToString(); + var username = filterContext.HttpContext.User.Identity.Name; + var loggerName = $"{controller}Controller.{action}"; + var @params = string.Join(", ", filterContext.ActionParameters.Select(i => $"{i.Key}: {{{i.Value}}}")); + + var hostAddress = filterContext.HttpContext.Request.UserHostAddress; + + LogManager.GetLogger(loggerName) + .Info("UserHostAddress: {0}, username: {1}, params: {{{2}}}", hostAddress, username, @params); + } + base.OnActionExecuting(filterContext); + } + } +} \ No newline at end of file diff --git a/InventoryTraker.Web/Controllers/ControllerBase.cs b/InventoryTraker.Web/Controllers/ControllerBase.cs index 082f38d..d03419f 100644 --- a/InventoryTraker.Web/Controllers/ControllerBase.cs +++ b/InventoryTraker.Web/Controllers/ControllerBase.cs @@ -12,7 +12,7 @@ namespace InventoryTraker.Web.Controllers return new BetterJsonResult {Data = model}; } - protected IEnumerable GetModelStateErrorList() + protected string[] GetModelStateErrorList() { var errorList = from kvp in ModelState @@ -20,13 +20,18 @@ namespace InventoryTraker.Web.Controllers let errors = string.Join(", ", kvp.Value.Errors.Select(e => e.ErrorMessage)) let msg = kvp.Key + ": " + errors select msg; - return errorList; + return errorList.ToArray(); } protected JsonResult GetModelStateErrorListJson() + { + return GetErrorListJson(GetModelStateErrorList()); + } + + protected JsonResult GetErrorListJson(params string[] errors) { var betterJsonResult = new BetterJsonResult(); - foreach (var err in GetModelStateErrorList()) + foreach (var err in errors) betterJsonResult.AddError(err); return betterJsonResult; } diff --git a/InventoryTraker.Web/Controllers/InventoryController.cs b/InventoryTraker.Web/Controllers/InventoryController.cs index 6fcb78c..91d374b 100644 --- a/InventoryTraker.Web/Controllers/InventoryController.cs +++ b/InventoryTraker.Web/Controllers/InventoryController.cs @@ -4,7 +4,6 @@ using System.Linq; using System.Web.Mvc; using AutoMapper; using AutoMapper.QueryableExtensions; -using InventoryTraker.Web.ActionResults; using InventoryTraker.Web.Core; using InventoryTraker.Web.Data; using InventoryTraker.Web.Models; @@ -57,6 +56,10 @@ namespace InventoryTraker.Web.Controllers var inventory = Mapper.Map(form); inventory.InventoryType = _context.InventoryTypes.Find(form.InventoryTypeId); + + if (inventory.InventoryType == null) + return GetErrorListJson("Inventory Type not found"); + _context.Inventories.Add(inventory); inventory.Transactions = new List { @@ -81,35 +84,105 @@ namespace InventoryTraker.Web.Controllers if (!ModelState.IsValid) return GetModelStateErrorListJson(); + var errors = new List(); + foreach (var quantityForm in form.InventoryQuantities) { var inventory = _context.Inventories.Find(quantityForm.InventoryId); // check if it's really there if (inventory == null) - return BetterJsonResult.Error($"Inventory {quantityForm.InventoryId} not found"); - + { + errors.Add($"'{quantityForm.InventoryId}' not found"); + continue; + } + if (inventory.Quantity < quantityForm.Quantity) - return BetterJsonResult.Error( - $"Inventory {inventory.InventoryType.Name} has only {inventory.Quantity}, trying to remove {quantityForm.Quantity}"); + errors.Add( + $"'{inventory.InventoryType.Name}' has only {inventory.Quantity} qty, " + + $"cannot remove {quantityForm.Quantity}"); + inventory.Quantity -= quantityForm.Quantity; + var mostRecentTransaction = + inventory.Transactions.OrderByDescending(t => t.TransactionDate).First(); + if (form.DistributedDate < mostRecentTransaction.TransactionDate) + errors.Add($"'{inventory.InventoryType.Name}' most recent transaction " + + $"is {mostRecentTransaction.TransactionDate.ToShortDateString()}. " + + $"Cannot add a transaction on {form.DistributedDate.ToShortDateString()}."); + inventory.Transactions.Add(new Transaction { TransactionType = TransactionType.Distributed, RemovedQuantity = quantityForm.Quantity, CurrentQuantity = inventory.Quantity, - Memo = "Distributed to " + form.Destination, + Memo = + string.IsNullOrEmpty(form.Memo) + ? $"{form.Memo} | " + : "" + + $"Distributed to {form.Destination}", Timestamp = DateTime.Now, TransactionDate = form.DistributedDate }); } - + + if (errors.Any()) + return GetErrorListJson(errors.ToArray()); + _context.SaveChanges(); return BetterJson(AllInventory() .ProjectTo() .ToArray()); } + + public JsonResult Remove(InventoryRemoveForm form) + { + if (!ModelState.IsValid) + return GetModelStateErrorListJson(); + + var errors = new List(); + + var inventory = _context.Inventories.Find(form.InventoryId); + + // check if it's really there + if (inventory == null) + { + errors.Add($"'{form.InventoryId}' not found"); + } + else + { + if (inventory.Quantity < form.Quantity) + errors.Add( + $"'{inventory.InventoryType.Name}' has only {inventory.Quantity} qty, " + + $"cannot remove {form.Quantity}"); + + inventory.Quantity -= form.Quantity; + + var mostRecentTransaction = + inventory.Transactions.OrderByDescending(t => t.TransactionDate).First(); + if (form.RemovedDate < mostRecentTransaction.TransactionDate) + errors.Add($"'{inventory.InventoryType.Name}' most recent transaction " + + $"is {mostRecentTransaction.TransactionDate.ToShortDateString()}. " + + $"Cannot add a transaction on {form.RemovedDate.ToShortDateString()}."); + + inventory.Transactions.Add(new Transaction + { + TransactionType = form.TransactionType, + RemovedQuantity = form.Quantity, + CurrentQuantity = inventory.Quantity, + Memo = form.Memo, + Timestamp = DateTime.Now, + TransactionDate = form.RemovedDate + }); + } + + if (errors.Any()) + return GetErrorListJson(errors.ToArray()); + + _context.SaveChanges(); + + return BetterJson(Mapper.Map(inventory)); + } } } \ No newline at end of file diff --git a/InventoryTraker.Web/Controllers/TransactionController.cs b/InventoryTraker.Web/Controllers/TransactionController.cs index 725c08b..8856c23 100644 --- a/InventoryTraker.Web/Controllers/TransactionController.cs +++ b/InventoryTraker.Web/Controllers/TransactionController.cs @@ -2,9 +2,7 @@ using System.Collections.Generic; using System.Linq; using System.Web.Mvc; -using AutoMapper; using AutoMapper.QueryableExtensions; -using InventoryTraker.Web.ActionResults; using InventoryTraker.Web.Core; using InventoryTraker.Web.Data; using InventoryTraker.Web.Models; @@ -35,7 +33,7 @@ namespace InventoryTraker.Web.Controllers return BetterJson(viewModels); } - public JsonResult GetTransactions(int? pageNumber, int? pageSize, int? inventoryId) + public JsonResult Get(int? pageNumber, int? pageSize, int? inventoryId) { IQueryable query = _context.Transactions @@ -56,5 +54,38 @@ namespace InventoryTraker.Web.Controllers .ToArray(); return BetterJson(new { totalItems, transactions }); } + + public JsonResult Delete(int transactionId) + { + var transaction = _context.Transactions.Find(transactionId); + + if (transaction == null) + return GetErrorListJson("Transaction not found"); + + var inventory = transaction.Inventory; + var inventoryId = inventory.Id; + + var inventoryTransactionsMostRecent = + inventory + .Transactions.OrderByDescending(t => t.TransactionDate) + .First() == transaction; + + if (!inventoryTransactionsMostRecent) + return GetErrorListJson("Delete permitted only when transaction is the most recent"); + + // roll back that transaction + inventory.Quantity -= transaction.AddedQuantity; + inventory.Quantity += transaction.RemovedQuantity; + + inventory.Transactions.Remove(transaction); + _context.Transactions.Remove(transaction); + + if (!inventory.Transactions.Any()) + _context.Inventories.Remove(inventory); + + _context.SaveChanges(); + + return BetterJson(new { inventoryId}); + } } } \ No newline at end of file diff --git a/InventoryTraker.Web/Core/Transaction.cs b/InventoryTraker.Web/Core/Transaction.cs index d3551d0..fbf9de0 100644 --- a/InventoryTraker.Web/Core/Transaction.cs +++ b/InventoryTraker.Web/Core/Transaction.cs @@ -28,12 +28,4 @@ namespace InventoryTraker.Web.Core [Required] public DateTime Timestamp { get; set; } } - - public enum TransactionType - { - Added, - Distributed, - Expired, - Loss - } } \ No newline at end of file diff --git a/InventoryTraker.Web/Core/TransactionType.cs b/InventoryTraker.Web/Core/TransactionType.cs new file mode 100644 index 0000000..90b4273 --- /dev/null +++ b/InventoryTraker.Web/Core/TransactionType.cs @@ -0,0 +1,10 @@ +namespace InventoryTraker.Web.Core +{ + public enum TransactionType + { + Added, + Distributed, + Expired, + Loss + } +} \ No newline at end of file diff --git a/InventoryTraker.Web/InventoryTraker.Web.csproj b/InventoryTraker.Web/InventoryTraker.Web.csproj index 9ca85bf..87925e1 100644 --- a/InventoryTraker.Web/InventoryTraker.Web.csproj +++ b/InventoryTraker.Web/InventoryTraker.Web.csproj @@ -52,7 +52,7 @@ True - ..\packages\CsvHelper.2.16.0.0\lib\net45\CsvHelper.dll + ..\packages\CsvHelper.2.16.3.0\lib\net45\CsvHelper.dll True @@ -132,6 +132,14 @@ ..\packages\Newtonsoft.Json.6.0.7\lib\net45\Newtonsoft.Json.dll True + + ..\packages\NLog.4.3.8\lib\net45\NLog.dll + True + + + ..\packages\NLog.Web.4.2.1\lib\net35\NLog.Web.dll + True + ..\packages\Owin.1.0\lib\net40\Owin.dll True @@ -229,6 +237,8 @@ + + @@ -278,6 +288,14 @@ + + + + Always + + + Designer + @@ -300,6 +318,7 @@ + @@ -311,6 +330,7 @@ + @@ -328,6 +348,7 @@ + diff --git a/InventoryTraker.Web/Models/InventoryRemoveForm.cs b/InventoryTraker.Web/Models/InventoryRemoveForm.cs new file mode 100644 index 0000000..e76efe7 --- /dev/null +++ b/InventoryTraker.Web/Models/InventoryRemoveForm.cs @@ -0,0 +1,23 @@ +using System; +using System.ComponentModel.DataAnnotations; +using InventoryTraker.Web.Core; + +namespace InventoryTraker.Web.Models +{ + public class InventoryRemoveForm + { + [Required] + public int InventoryId { get; set; } + + [Required, Range(1, int.MaxValue, ErrorMessage = "Quantity must be greater than 0")] + public int Quantity { get; set; } + + [Required] + public TransactionType TransactionType { get; set; } + + [Required, Display(Name = "Removed Date")] + public DateTime RemovedDate { get; set; } + + public string Memo { get; set; } + } +} \ No newline at end of file diff --git a/InventoryTraker.Web/NLog.config b/InventoryTraker.Web/NLog.config new file mode 100644 index 0000000..68ae0ea --- /dev/null +++ b/InventoryTraker.Web/NLog.config @@ -0,0 +1,22 @@ + + + + + + + + + + + + diff --git a/InventoryTraker.Web/NLog.xsd b/InventoryTraker.Web/NLog.xsd new file mode 100644 index 0000000..2104459 --- /dev/null +++ b/InventoryTraker.Web/NLog.xsd @@ -0,0 +1,2655 @@ + + + + + + + + + + + + + + + Watch config file for changes and reload automatically. + + + + + Print internal NLog messages to the console. Default value is: false + + + + + Print internal NLog messages to the console error output. Default value is: false + + + + + Write internal NLog messages to the specified file. + + + + + Log level threshold for internal log messages. Default value is: Info. + + + + + Global log level threshold for application log messages. Messages below this level won't be logged.. + + + + + Pass NLog internal exceptions to the application. Default value is: false. + + + + + Write internal NLog messages to the the System.Diagnostics.Trace. Default value is: false + + + + + + + + + + + + + + Make all targets within this section asynchronous (creates additional threads but the calling thread isn't blocked by any target writes). + + + + + + + + + + + + + + + + + Prefix for targets/layout renderers/filters/conditions loaded from this assembly. + + + + + Load NLog extensions from the specified file (*.dll) + + + + + Load NLog extensions from the specified assembly. Assembly name should be fully qualified. + + + + + + + + + + Name of the logger. May include '*' character which acts like a wildcard. Allowed forms are: *, Name, *Name, Name* and *Name* + + + + + Comma separated list of levels that this rule matches. + + + + + Minimum level that this rule matches. + + + + + Maximum level that this rule matches. + + + + + Level that this rule matches. + + + + + Comma separated list of target names. + + + + + Ignore further rules if this one matches. + + + + + Enable or disable logging rule. Disabled rules are ignored. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Name of the file to be included. The name is relative to the name of the current config file. + + + + + Ignore any errors in the include file. + + + + + + + Variable name. + + + + + Variable value. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Name of the target. + + + + + Layout used to format log messages. + + + + + Indicates whether to add <!-- --> comments around all written texts. + + + + + + + + + + + + + + + + + + Name of the target. + + + + + Number of log events that should be processed in a batch by the lazy writer thread. + + + + + Action to be taken when the lazy writer thread request queue count exceeds the set limit. + + + + + Limit on the number of requests in the lazy writer thread request queue. + + + + + Time in milliseconds to sleep between batches. + + + + + + + + + + + + + + + + + + + + + Name of the target. + + + + + + + + + + + + + + + + + Name of the target. + + + + + Number of log events to be buffered. + + + + + Timeout (in milliseconds) after which the contents of buffer will be flushed if there's no write in the specified period of time. Use -1 to disable timed flushes. + + + + + Indicates whether to use sliding timeout. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Name of the target. + + + + + Encoding to be used. + + + + + Instance of that is used to format log messages. + + + + + End of line value if a newline is appended at the end of log message . + + + + + Maximum message size in bytes. + + + + + Indicates whether to append newline at the end of log message. + + + + + Action that should be taken if the will be more connections than . + + + + + Action that should be taken if the message is larger than maxMessageSize. + + + + + Indicates whether to keep connection open whenever possible. + + + + + Size of the connection cache (number of connections which are kept alive). + + + + + Maximum current connections. 0 = no maximum. + + + + + Network address. + + + + + Maximum queue size. + + + + + Indicates whether to include source info (file name and line number) in the information sent over the network. + + + + + NDC item separator. + + + + + Indicates whether to include stack contents. + + + + + Indicates whether to include call site (class and method name) in the information sent over the network. + + + + + AppInfo field. By default it's the friendly name of the current AppDomain. + + + + + Indicates whether to include NLog-specific extensions to log4j schema. + + + + + Indicates whether to include dictionary contents. + + + + + + + + + + + + + + + + + + + + + + + + + + + Layout that should be use to calcuate the value for the parameter. + + + + + Viewer parameter name. + + + + + + + + + + + + + + + + + + + + Name of the target. + + + + + Text to be rendered. + + + + + Header. + + + + + Footer. + + + + + Indicates whether to use default row highlighting rules. + + + + + The encoding for writing messages to the . + + + + + Indicates whether the error stream (stderr) should be used instead of the output stream (stdout). + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Condition that must be met in order to set the specified foreground and background color. + + + + + Background color. + + + + + Foreground color. + + + + + + + + + + + + + + + + Indicates whether to ignore case when comparing texts. + + + + + Regular expression to be matched. You must specify either text or regex. + + + + + Text to be matched. You must specify either text or regex. + + + + + Indicates whether to match whole words only. + + + + + Compile the ? This can improve the performance, but at the costs of more memory usage. If false, the Regex Cache is used. + + + + + Background color. + + + + + Foreground color. + + + + + + + + + + + + + + + + + Name of the target. + + + + + Text to be rendered. + + + + + Header. + + + + + Footer. + + + + + Indicates whether to send the log messages to the standard error instead of the standard output. + + + + + The encoding for writing messages to the . + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Name of the target. + + + + + Connection string. When provided, it overrides the values specified in DBHost, DBUserName, DBPassword, DBDatabase. + + + + + Name of the connection string (as specified in <connectionStrings> configuration section. + + + + + Database name. If the ConnectionString is not provided this value will be used to construct the "Database=" part of the connection string. + + + + + Database host name. If the ConnectionString is not provided this value will be used to construct the "Server=" part of the connection string. + + + + + Database password. If the ConnectionString is not provided this value will be used to construct the "Password=" part of the connection string. + + + + + Name of the database provider. + + + + + Database user name. If the ConnectionString is not provided this value will be used to construct the "User ID=" part of the connection string. + + + + + Indicates whether to keep the database connection open between the log events. + + + + + Obsolete - value will be ignored! The logging code always runs outside of transaction. Gets or sets a value indicating whether to use database transactions. Some data providers require this. + + + + + Connection string using for installation and uninstallation. If not provided, regular ConnectionString is being used. + + + + + Text of the SQL command to be run on each log level. + + + + + Type of the SQL command to be run on each log level. + + + + + + + + + + + + + + + + + + + + + + + Type of the command. + + + + + Connection string to run the command against. If not provided, connection string from the target is used. + + + + + Indicates whether to ignore failures. + + + + + Command text. + + + + + + + + + + + + + + Layout that should be use to calcuate the value for the parameter. + + + + + Database parameter name. + + + + + Database parameter precision. + + + + + Database parameter scale. + + + + + Database parameter size. + + + + + + + + + + + + + + + Name of the target. + + + + + Text to be rendered. + + + + + Header. + + + + + Footer. + + + + + + + + + + + + + + + Name of the target. + + + + + Layout used to format log messages. + + + + + + + + + + + + + + + + + + + + + + + Name of the target. + + + + + Layout used to format log messages. + + + + + Layout that renders event Category. + + + + + Layout that renders event ID. + + + + + Name of the Event Log to write to. This can be System, Application or any user-defined name. + + + + + Name of the machine on which Event Log service is running. + + + + + Value to be used as the event Source. + + + + + Action to take if the message is larger than the option. + + + + + Optional entrytype. When not set, or when not convertable to then determined by + + + + + Message length limit to write to the Event Log. + + + + + + + + + + + + + + + + + + + + + + Name of the target. + + + + + Indicates whether to return to the first target after any successful write. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Name of the target. + + + + + Text to be rendered. + + + + + Header. + + + + + Footer. + + + + + File encoding. + + + + + Line ending mode. + + + + + Way file archives are numbered. + + + + + Name of the file to be used for an archive. + + + + + Indicates whether to automatically archive log files every time the specified time passes. + + + + + Size in bytes above which log files will be automatically archived. Warning: combining this with isn't supported. We cannot create multiple archive files, if they should have the same name. Choose: + + + + + Indicates whether to compress archive files into the zip archive format. + + + + + Maximum number of archive files that should be kept. + + + + + Is the an absolute or relative path? + + + + + Is the an absolute or relative path? + + + + + Cleanup invalid values in a filename, e.g. slashes in a filename. If set to true, this can impact the performance of massive writes. If set to false, nothing gets written when the filename is wrong. + + + + + Gets or set a value indicating whether a managed file stream is forced, instead of used the native implementation. + + + + + Name of the file to write to. + + + + + Value specifying the date format to use when archiving files. + + + + + Indicates whether to archive old log file on startup. + + + + + Indicates whether to create directories if they do not exist. + + + + + Indicates whether to enable log file(s) to be deleted. + + + + + File attributes (Windows only). + + + + + Indicates whether to delete old log file on startup. + + + + + Indicates whether to replace file contents on each write instead of appending log message at the end. + + + + + Delay in milliseconds to wait before attempting to write to the file again. + + + + + Indicates whether to keep log file open instead of opening and closing it on each logging event. + + + + + Maximum number of log filenames that should be stored as existing. + + + + + Indicates whether concurrent writes to the log file by multiple processes on different network hosts. + + + + + Number of files to be kept open. Setting this to a higher value may improve performance in a situation where a single File target is writing to many files (such as splitting by level or by logger). + + + + + Maximum number of seconds that files are kept open. If this number is negative the files are not automatically closed after a period of inactivity. + + + + + Log file buffer size in bytes. + + + + + Indicates whether to automatically flush the file buffers after each log message. + + + + + Number of times the write is appended on the file before NLog discards the log message. + + + + + Indicates whether concurrent writes to the log file by multiple processes on the same host. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Name of the target. + + + + + Condition expression. Log events who meet this condition will be forwarded to the wrapped target. + + + + + + + + + + + + + + + + + + + + + Name of the target. + + + + + Windows domain name to change context to. + + + + + Required impersonation level. + + + + + Type of the logon provider. + + + + + Logon Type. + + + + + User account password. + + + + + Indicates whether to revert to the credentials of the process instead of impersonating another user. + + + + + Username to change context to. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Name of the target. + + + + + Endpoint address. + + + + + Name of the endpoint configuration in WCF configuration file. + + + + + Indicates whether to use a WCF service contract that is one way (fire and forget) or two way (request-reply) + + + + + Client ID. + + + + + Indicates whether to include per-event properties in the payload sent to the server. + + + + + Indicates whether to use binary message encoding. + + + + + + + + + + + + + + Layout that should be use to calculate the value for the parameter. + + + + + Name of the parameter. + + + + + Type of the parameter. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Name of the target. + + + + + Text to be rendered. + + + + + Header. + + + + + Footer. + + + + + Indicates whether to send message as HTML instead of plain text. + + + + + Encoding to be used for sending e-mail. + + + + + Indicates whether to add new lines between log entries. + + + + + CC email addresses separated by semicolons (e.g. john@domain.com;jane@domain.com). + + + + + Recipients' email addresses separated by semicolons (e.g. john@domain.com;jane@domain.com). + + + + + BCC email addresses separated by semicolons (e.g. john@domain.com;jane@domain.com). + + + + + Mail message body (repeated for each log message send in one mail). + + + + + Mail subject. + + + + + Sender's email address (e.g. joe@domain.com). + + + + + Indicates whether NewLine characters in the body should be replaced with tags. + + + + + Priority used for sending mails. + + + + + Indicates the SMTP client timeout. + + + + + SMTP Server to be used for sending. + + + + + SMTP Authentication mode. + + + + + Username used to connect to SMTP server (used when SmtpAuthentication is set to "basic"). + + + + + Password used to authenticate against SMTP server (used when SmtpAuthentication is set to "basic"). + + + + + Indicates whether SSL (secure sockets layer) should be used when communicating with SMTP server. + + + + + Port number that SMTP Server is listening on. + + + + + Indicates whether the default Settings from System.Net.MailSettings should be used. + + + + + Folder where applications save mail messages to be processed by the local SMTP server. + + + + + Specifies how outgoing email messages will be handled. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Name of the target. + + + + + Layout used to format log messages. + + + + + + + + + + + + + + + + + + + + + + Name of the target. + + + + + Layout used to format log messages. + + + + + Encoding to be used when writing text to the queue. + + + + + Indicates whether to use the XML format when serializing message. This will also disable creating queues. + + + + + Indicates whether to check if a queue exists before writing to it. + + + + + Indicates whether to create the queue if it doesn't exists. + + + + + Label to associate with each message. + + + + + Name of the queue to write to. + + + + + Indicates whether to use recoverable messages (with guaranteed delivery). + + + + + + + + + + + + + + + + + Name of the target. + + + + + Class name. + + + + + Method name. The method must be public and static. Use the AssemblyQualifiedName , https://msdn.microsoft.com/en-us/library/system.type.assemblyqualifiedname(v=vs.110).aspx e.g. + + + + + + + + + + + + + + + + + + + + + + + + + + Name of the target. + + + + + Layout used to format log messages. + + + + + Encoding to be used. + + + + + End of line value if a newline is appended at the end of log message . + + + + + Maximum message size in bytes. + + + + + Indicates whether to append newline at the end of log message. + + + + + Action that should be taken if the will be more connections than . + + + + + Action that should be taken if the message is larger than maxMessageSize. + + + + + Network address. + + + + + Size of the connection cache (number of connections which are kept alive). + + + + + Indicates whether to keep connection open whenever possible. + + + + + Maximum current connections. 0 = no maximum. + + + + + Maximum queue size. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Name of the target. + + + + + Encoding to be used. + + + + + Instance of that is used to format log messages. + + + + + End of line value if a newline is appended at the end of log message . + + + + + Maximum message size in bytes. + + + + + Indicates whether to append newline at the end of log message. + + + + + Action that should be taken if the will be more connections than . + + + + + Action that should be taken if the message is larger than maxMessageSize. + + + + + Indicates whether to keep connection open whenever possible. + + + + + Size of the connection cache (number of connections which are kept alive). + + + + + Maximum current connections. 0 = no maximum. + + + + + Network address. + + + + + Maximum queue size. + + + + + Indicates whether to include source info (file name and line number) in the information sent over the network. + + + + + NDC item separator. + + + + + Indicates whether to include stack contents. + + + + + Indicates whether to include call site (class and method name) in the information sent over the network. + + + + + AppInfo field. By default it's the friendly name of the current AppDomain. + + + + + Indicates whether to include NLog-specific extensions to log4j schema. + + + + + Indicates whether to include dictionary contents. + + + + + + + + + + + + + + + + Name of the target. + + + + + Layout used to format log messages. + + + + + Indicates whether to perform layout calculation. + + + + + + + + + + + + + + + Name of the target. + + + + + Layout used to format log messages. + + + + + + + + + + + + + + + + + + + + + Name of the target. + + + + + Indicates whether performance counter should be automatically created. + + + + + Name of the performance counter category. + + + + + Counter help text. + + + + + Name of the performance counter. + + + + + Performance counter type. + + + + + The value by which to increment the counter. + + + + + Performance counter instance name. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Name of the target. + + + + + Default filter to be applied when no specific rule matches. + + + + + + + + + + + + + Condition to be tested. + + + + + Resulting filter to be applied when the condition matches. + + + + + + + + + + + + Name of the target. + + + + + + + + + + + + + + + Name of the target. + + + + + Number of times to repeat each log message. + + + + + + + + + + + + + + + + Name of the target. + + + + + Number of retries that should be attempted on the wrapped target in case of a failure. + + + + + Time to wait between retries in milliseconds. + + + + + + + + + + + + + + Name of the target. + + + + + + + + + + + + + + Name of the target. + + + + + + + + + + + + + + + Name of the target. + + + + + Layout used to format log messages. + + + + + + + + + + + + + + + + + + + + + Name of the target. + + + + + Should we include the BOM (Byte-order-mark) for UTF? Influences the property. This will only work for UTF-8. + + + + + Encoding. + + + + + Web service method name. Only used with Soap. + + + + + Web service namespace. Only used with Soap. + + + + + Protocol to be used when calling web service. + + + + + Web service URL. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Footer layout. + + + + + Header layout. + + + + + Body layout (can be repeated multiple times). + + + + + Custom column delimiter value (valid when ColumnDelimiter is set to 'Custom'). + + + + + Column delimiter. + + + + + Quote Character. + + + + + Quoting mode. + + + + + Indicates whether CVS should include header. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Layout of the column. + + + + + Name of the column. + + + + + + + + + + + + + + Option to render the empty object value {} + + + + + Option to suppress the extra spaces in the output json + + + + + + + + + + + + + + Determines wether or not this attribute will be Json encoded. + + + + + Layout that will be rendered as the attribute's value. + + + + + Name of the attribute. + + + + + + + + + + + + + + Footer layout. + + + + + Header layout. + + + + + Body layout (can be repeated multiple times). + + + + + + + + + + + + + + + + + + + + + Layout text. + + + + + + + + + + + + + + + Action to be taken when filter matches. + + + + + Condition expression. + + + + + + + + + + + + + + + + + + + + + + + + + + Action to be taken when filter matches. + + + + + Indicates whether to ignore case when comparing strings. + + + + + Layout to be used to filter log messages. + + + + + Substring to be matched. + + + + + + + + + + + + + + + + + Action to be taken when filter matches. + + + + + String to compare the layout to. + + + + + Indicates whether to ignore case when comparing strings. + + + + + Layout to be used to filter log messages. + + + + + + + + + + + + + + + + + Action to be taken when filter matches. + + + + + Indicates whether to ignore case when comparing strings. + + + + + Layout to be used to filter log messages. + + + + + Substring to be matched. + + + + + + + + + + + + + + + + + Action to be taken when filter matches. + + + + + String to compare the layout to. + + + + + Indicates whether to ignore case when comparing strings. + + + + + Layout to be used to filter log messages. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/InventoryTraker.Web/Views/Profile/Index.cshtml b/InventoryTraker.Web/Views/Profile/Index.cshtml index 315cba1..dbc6e02 100644 --- a/InventoryTraker.Web/Views/Profile/Index.cshtml +++ b/InventoryTraker.Web/Views/Profile/Index.cshtml @@ -45,6 +45,6 @@ saveUrl: url }); - window.app.constant('model', @Html.JsonFor(Model)) + window.app.constant('model', @Html.JsonFor(Model)); } \ No newline at end of file diff --git a/InventoryTraker.Web/css/layout.css b/InventoryTraker.Web/css/layout.css index 7c5561f..933c709 100644 --- a/InventoryTraker.Web/css/layout.css +++ b/InventoryTraker.Web/css/layout.css @@ -70,7 +70,11 @@ textarea { } th.control-column { - width: 1%; + width: 50px; +} + +.modal-window-slim { + /*width: 300px;*/ } /*.has-feedback ng-transclude ~ input-validation-icons .form-control-feedback { diff --git a/InventoryTraker.Web/js/inventory/InventoryAddDirective.js b/InventoryTraker.Web/js/inventory/InventoryAddDirective.js index e6c8817..fd80dc4 100644 --- a/InventoryTraker.Web/js/inventory/InventoryAddDirective.js +++ b/InventoryTraker.Web/js/inventory/InventoryAddDirective.js @@ -18,10 +18,10 @@ vm.add = add; vm.saving = false; - vm.inventory = {}; + vm.inventory = { }; vm.inventoryTypes = inventoryTypeSvc.inventoryTypes; + vm.errorMessages = []; - vm.errorMessage = null; vm.quantity = quantity; function zeroNaN(v) { @@ -37,6 +37,11 @@ return vm.inventory.quantity > 0 ? vm.inventory.quantity : ""; } + $scope.$watch('vm.commodity', function (newValue) { + if (newValue) + vm.inventory.inventoryTypeId = newValue.id; + }); + function add() { vm.saving = true; inventorySvc.add(vm.inventory) @@ -45,17 +50,11 @@ $scope.$close(); }) .error(function(data) { - vm.errorMessage = - 'There was a problem adding the inventory: ' + data.errorMessage; + vm.errorMessages = angular.copy(data.errorMessages, vm.errorMessages); }) .finally(function() { vm.saving = false; }); } - - $scope.$watch('vm.commodity', function (newValue, oldValue) { - if (newValue) - vm.inventory.inventoryTypeId = newValue.id; - }); } })(); \ No newline at end of file diff --git a/InventoryTraker.Web/js/inventory/InventoryDistributeDirective.js b/InventoryTraker.Web/js/inventory/InventoryDistributeDirective.js index b2960f1..4eff0d1 100644 --- a/InventoryTraker.Web/js/inventory/InventoryDistributeDirective.js +++ b/InventoryTraker.Web/js/inventory/InventoryDistributeDirective.js @@ -19,7 +19,7 @@ vm.saving = false; vm.quantities = angular.copy(inventorySvc.inventories); vm.distribution = {}; - vm.errorMessage = null; + vm.errorMessages = []; function getInventoryDistributeQuantities(inventory) { var invQty = []; @@ -39,8 +39,7 @@ $scope.$close(); }) .error(function(data) { - vm.errorMessage = - 'There was a problem distributing the inventory: ' + data.errorMessage; + vm.errorMessages = angular.copy(data.errorMessages, vm.errorMessages); }) .finally(function() { vm.saving = false; diff --git a/InventoryTraker.Web/js/inventory/InventoryEditDirective.js b/InventoryTraker.Web/js/inventory/InventoryEditDirective.js index f7c5c52..93cf8fa 100644 --- a/InventoryTraker.Web/js/inventory/InventoryEditDirective.js +++ b/InventoryTraker.Web/js/inventory/InventoryEditDirective.js @@ -14,25 +14,49 @@ } } - controller.$inject = ['$scope', 'inventorySvc', 'transactionSvc']; - function controller($scope, inventorySvc, transactionSvc) { + controller.$inject = ['$scope', '$uibModal', 'inventorySvc', 'transactionSvc']; + function controller($scope, $uibModal, inventorySvc, transactionSvc) { var vm = this; - vm.inventory = angular.copy($scope.inventory); + vm.inventory = $scope.inventory; vm.transactions = []; - function loadTransactions() { + function refreshTransactions() { + vm.loadingTransactions = true; + vm.transactions = []; transactionSvc .filterByInventoryId(vm.inventory.id) - .success(function(data) { - angular.merge(vm.transactions, data.transactions); - }); + .success(function (data) { + if (data.transactions.length === 0) { + $scope.$parent.$close(); + } else { + angular.copy(data.transactions, vm.transactions); + } + }) + .finally(function(){vm.loadingTransactions = false;}); } - - loadTransactions(); + refreshTransactions(); // initial call vm.save = save; + vm.deleteTransaction = deleteTransaction; + vm.removeInventory = removeInventory; vm.saving = false; - vm.errorMessage = null; + vm.errorMessages = []; + vm.confirmDeleteTransaction = false; + vm.loadingTransactions = false; + + function deleteTransaction(transactionId) { + vm.confirmDeleteTransaction = false; + transactionSvc + .deleteTransaction(transactionId) + .success(refreshTransactions); + } + + function removeInventory() { + $uibModal.open({ + template: '', + scope: angular.extend($scope.$new(true), { inventory: vm.inventory }) + }).closed.then(refreshTransactions); + } function save() { vm.saving = true; @@ -42,7 +66,7 @@ $scope.$parent.$close(); }) .error(function(data) { - vm.errorMessage = 'There was a problem saving changes to the inventory: ' + data.errorMessage; + vm.errorMessages = angular.copy(data.errorMessages, vm.errorMessages); }) .finally(function() { vm.saving = false; diff --git a/InventoryTraker.Web/js/inventory/InventoryListDirective.js b/InventoryTraker.Web/js/inventory/InventoryListDirective.js index b2ca1e0..7377d41 100644 --- a/InventoryTraker.Web/js/inventory/InventoryListDirective.js +++ b/InventoryTraker.Web/js/inventory/InventoryListDirective.js @@ -17,6 +17,7 @@ vm.inventories = $scope.inventories; vm.edit = edit; + vm.remove = remove; function edit(inventory) { $uibModal.open({ @@ -24,5 +25,12 @@ scope: angular.extend($scope.$new(true), { inventory: inventory }) }); } + + function remove(inventory) { + $uibModal.open({ + template: '', + scope: angular.extend($scope.$new(true), { inventory: inventory }) + }); + } } })(); \ No newline at end of file diff --git a/InventoryTraker.Web/js/inventory/inventoryInfoDirective.js b/InventoryTraker.Web/js/inventory/inventoryInfoDirective.js new file mode 100644 index 0000000..555e1b9 --- /dev/null +++ b/InventoryTraker.Web/js/inventory/inventoryInfoDirective.js @@ -0,0 +1,20 @@ +(function() { + "use strict"; + + window.app.directive('inventoryInfo', inventoryInfo); + + function inventoryInfo() { + return { + scope: { "inventory": "=" }, + templateUrl: '/inventory/template/inventoryInfo.tmpl.cshtml', + controller: controller, + controllerAs: 'vm' + } + } + + controller.$inject = ['$scope']; + function controller($scope) { + var vm = this; + vm.inventory = $scope.inventory; + } +})(); \ No newline at end of file diff --git a/InventoryTraker.Web/js/inventory/inventoryRemoveDirective.js b/InventoryTraker.Web/js/inventory/inventoryRemoveDirective.js new file mode 100644 index 0000000..d4779bc --- /dev/null +++ b/InventoryTraker.Web/js/inventory/inventoryRemoveDirective.js @@ -0,0 +1,45 @@ +(function() { + "use strict"; + + window.app.directive('inventoryRemove', inventoryRemove); + + function inventoryRemove() { + return { + scope: { "inventory": "=" }, + templateUrl: '/inventory/template/inventoryRemove.tmpl.cshtml', + controller: controller, + controllerAs: 'vm' + } + } + + controller.$inject = ['$scope', 'inventorySvc']; + function controller($scope, inventorySvc) { + var vm = this; + vm.inventory = $scope.inventory; + + vm.save = save; + vm.saving = false; + vm.removeForm = { + inventoryId: vm.inventory.id, + quantity: vm.inventory.quantity, + transactionType: vm.inventory.isExpired ? "Expired" : null + }; + + vm.errorMessages = []; + + function save() { + vm.saving = true; + inventorySvc.remove(vm.removeForm) + .success(function (data) { + //Close the modal + $scope.$parent.$close(); + }) + .error(function (data) { + vm.errorMessages = angular.copy(data.errorMessages, vm.errorMessages); + }) + .finally(function () { + vm.saving = false; + }); + } + } +})(); \ No newline at end of file diff --git a/InventoryTraker.Web/js/inventory/inventorySvc.js b/InventoryTraker.Web/js/inventory/inventorySvc.js index 5a76f6e..603ae72 100644 --- a/InventoryTraker.Web/js/inventory/inventorySvc.js +++ b/InventoryTraker.Web/js/inventory/inventorySvc.js @@ -1,8 +1,8 @@ (function() { window.app.factory('inventorySvc', inventorySvc); - inventorySvc.$inject = ['$http']; - function inventorySvc($http) { + inventorySvc.$inject = ['$http', '$filter']; + function inventorySvc($http, $filter) { var inventories = []; loadInventories(); @@ -11,8 +11,11 @@ add: add, distribute: distribute, update: update, + remove: remove, inventories: inventories, - getInventory: getInventory + get: get, + refresh: refresh, + find: find }; return svc; @@ -45,7 +48,34 @@ }); } - function getInventory(id) { + // remove quantity from this inventory + function remove(removeForm) { + var existingInventory = get(removeForm.inventoryId); + return $http.post('/Inventory/Remove', removeForm) + .success(function(data) { + angular.copy(data, existingInventory); + }); + } + + function get(id) { + var results = $filter('filter')(inventories, { id: id }); + if (results.length > 0) + return results[0]; + return null; + } + + // refresh this inventory from the server + function refresh(id) { + var existingInventory = get(id); + if (existingInventory) { + find(id) + .success(function(inventory) { + angular.copy(inventory, existingInventory); + }); + } + } + + function find(id) { return $http.post('/Inventory/Find', { id: id }); } } diff --git a/InventoryTraker.Web/js/inventory/templates/inventoryAdd.tmpl.cshtml b/InventoryTraker.Web/js/inventory/templates/inventoryAdd.tmpl.cshtml index 1a818e2..df38bb0 100644 --- a/InventoryTraker.Web/js/inventory/templates/inventoryAdd.tmpl.cshtml +++ b/InventoryTraker.Web/js/inventory/templates/inventoryAdd.tmpl.cshtml @@ -14,12 +14,15 @@