diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000..1ff0c423 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,63 @@ +############################################################################### +# Set default behavior to automatically normalize line endings. +############################################################################### +* text=auto + +############################################################################### +# Set default behavior for command prompt diff. +# +# This is need for earlier builds of msysgit that does not have it on by +# default for csharp files. +# Note: This is only used by command line +############################################################################### +#*.cs diff=csharp + +############################################################################### +# Set the merge driver for project and solution files +# +# Merging from the command prompt will add diff markers to the files if there +# are conflicts (Merging from VS is not affected by the settings below, in VS +# the diff markers are never inserted). Diff markers may cause the following +# file extensions to fail to load in VS. An alternative would be to treat +# these files as binary and thus will always conflict and require user +# intervention with every merge. To do so, just uncomment the entries below +############################################################################### +#*.sln merge=binary +#*.csproj merge=binary +#*.vbproj merge=binary +#*.vcxproj merge=binary +#*.vcproj merge=binary +#*.dbproj merge=binary +#*.fsproj merge=binary +#*.lsproj merge=binary +#*.wixproj merge=binary +#*.modelproj merge=binary +#*.sqlproj merge=binary +#*.wwaproj merge=binary + +############################################################################### +# behavior for image files +# +# image files are treated as binary by default. +############################################################################### +#*.jpg binary +#*.png binary +#*.gif binary + +############################################################################### +# diff behavior for common document formats +# +# Convert binary document formats to text before diffing them. This feature +# is only available from the command line. Turn it on by uncommenting the +# entries below. +############################################################################### +#*.doc diff=astextplain +#*.DOC diff=astextplain +#*.docx diff=astextplain +#*.DOCX diff=astextplain +#*.dot diff=astextplain +#*.DOT diff=astextplain +#*.pdf diff=astextplain +#*.PDF diff=astextplain +#*.rtf diff=astextplain +#*.RTF diff=astextplain diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..4ce6fdde --- /dev/null +++ b/.gitignore @@ -0,0 +1,340 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. +## +## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore + +# User-specific files +*.rsuser +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +[Aa][Rr][Mm]/ +[Aa][Rr][Mm]64/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ + +# Visual Studio 2015/2017 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# Visual Studio 2017 auto generated files +Generated\ Files/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUNIT +*.VisualState.xml +TestResult.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# Benchmark Results +BenchmarkDotNet.Artifacts/ + +# .NET Core +project.lock.json +project.fragment.lock.json +artifacts/ + +# StyleCop +StyleCopReport.xml + +# Files built by Visual Studio +*_i.c +*_p.c +*_h.h +*.ilk +*.meta +*.obj +*.iobj +*.pch +*.pdb +*.ipdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*_wpftmp.csproj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# Visual Studio Trace Files +*.e2e + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# JustCode is a .NET coding add-in +.JustCode + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# Note: Comment the next line if you want to checkin your web deploy settings, +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# The packages folder can be ignored because of Package Restore +**/[Pp]ackages/* +# except build/, which is used as an MSBuild target. +!**/[Pp]ackages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/[Pp]ackages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt +*.appx + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!?*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# Including strong name files can present a security risk +# (https://github.com/github/gitignore/pull/2483#issue-259490424) +#*.snk + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm +ServiceFabricBackup/ +*.rptproj.bak + +# SQL Server files +*.mdf +*.ldf +*.ndf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings +*.rptproj.rsuser +*- Backup*.rdl + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +node_modules/ + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# JetBrains Rider +.idea/ +*.sln.iml + +# CodeRush personal settings +.cr/personal + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Tabs Studio +*.tss + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs + +# OpenCover UI analysis results +OpenCover/ + +# Azure Stream Analytics local run output +ASALocalRun/ + +# MSBuild Binary and Structured Log +*.binlog + +# NVidia Nsight GPU debugger configuration file +*.nvuser + +# MFractors (Xamarin productivity tool) working folder +.mfractor/ + +# Local History for Visual Studio +.localhistory/ + +# BeatPulse healthcheck temp database +healthchecksdb \ No newline at end of file diff --git a/CashRegister/App.config b/CashRegister/App.config new file mode 100644 index 00000000..91de34f1 --- /dev/null +++ b/CashRegister/App.config @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/CashRegister/CashRegisterSolution.csproj b/CashRegister/CashRegisterSolution.csproj new file mode 100644 index 00000000..96845f89 --- /dev/null +++ b/CashRegister/CashRegisterSolution.csproj @@ -0,0 +1,137 @@ + + + + + + + + Debug + AnyCPU + {04F488D6-CA12-4DFB-98F8-6242A6CB1668} + Exe + CashRegister + CashRegister + v4.7.2 + 512 + true + true + + + + + AnyCPU + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + AnyCPU + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + ..\packages\CommandLineParser.2.7.82\lib\net461\CommandLine.dll + + + ..\packages\Microsoft.TestPlatform.ObjectModel.16.5.0\lib\net451\Microsoft.TestPlatform.CoreUtilities.dll + + + ..\packages\Microsoft.TestPlatform.ObjectModel.16.5.0\lib\net451\Microsoft.TestPlatform.PlatformAbstractions.dll + + + ..\packages\Microsoft.CodeCoverage.16.5.0\lib\net45\Microsoft.VisualStudio.CodeCoverage.Shim.dll + + + ..\packages\Microsoft.TestPlatform.ObjectModel.16.5.0\lib\net451\Microsoft.VisualStudio.TestPlatform.ObjectModel.dll + + + ..\packages\MSTest.TestFramework.2.1.0\lib\net45\Microsoft.VisualStudio.TestPlatform.TestFramework.dll + + + ..\packages\MSTest.TestFramework.2.1.0\lib\net45\Microsoft.VisualStudio.TestPlatform.TestFramework.Extensions.dll + + + ..\packages\NuGet.Frameworks.5.5.0-preview.2.6382\lib\net472\NuGet.Frameworks.dll + + + + ..\packages\System.Buffers.4.5.0\lib\netstandard2.0\System.Buffers.dll + + + ..\packages\System.Collections.Immutable.1.7.0\lib\netstandard2.0\System.Collections.Immutable.dll + + + + + ..\packages\System.Memory.4.5.3\lib\netstandard2.0\System.Memory.dll + + + + ..\packages\System.Numerics.Vectors.4.6.0-preview5.19224.8\lib\net46\System.Numerics.Vectors.dll + + + ..\packages\System.Reflection.Metadata.1.8.0\lib\netstandard2.0\System.Reflection.Metadata.dll + + + + ..\packages\System.Runtime.CompilerServices.Unsafe.4.7.0\lib\netstandard2.0\System.Runtime.CompilerServices.Unsafe.dll + + + ..\packages\System.Runtime.InteropServices.RuntimeInformation.4.3.0\lib\net45\System.Runtime.InteropServices.RuntimeInformation.dll + True + True + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. + + + + + + + + + + + + \ No newline at end of file diff --git a/CashRegister/Internal/Calculation/DataViolations.cs b/CashRegister/Internal/Calculation/DataViolations.cs new file mode 100644 index 00000000..fbaddb74 --- /dev/null +++ b/CashRegister/Internal/Calculation/DataViolations.cs @@ -0,0 +1,28 @@ +namespace CashRegister.Internal.Calculation +{ + /// + /// Possible data violations + /// used to manage error output + /// Assumption that these transactions are all purchases + /// no returns/credits are handled + /// + internal class DataViolations + { + public string Value { get; set; } + + private DataViolations(string value) + { + Value = value; + } + + public static DataViolations TooFewInputs => new DataViolations("Input contains too few items."); + public static DataViolations TooManyInputs => new DataViolations("Input contains too many items."); + public static DataViolations EmptyInput => new DataViolations("Input cannot be null."); + public static DataViolations NonNumeric => new DataViolations("Input cannot contain non numeric items."); + public static DataViolations InsufficentTendered => new DataViolations("Amount tendered is less than cost."); + public static DataViolations CostNegative => new DataViolations("Cost of transaction cannot be negative."); + public static DataViolations TenderedNegative => new DataViolations("Amount tendered cannot be negative."); + + public static DataViolations ExeceededcalculableLimits => new DataViolations("The transaction exceeds the calculation limits of the system."); + } +} \ No newline at end of file diff --git a/CashRegister/Internal/Calculation/Transaction.cs b/CashRegister/Internal/Calculation/Transaction.cs new file mode 100644 index 00000000..18f1aed7 --- /dev/null +++ b/CashRegister/Internal/Calculation/Transaction.cs @@ -0,0 +1,200 @@ +using System; +using System.Collections.Generic; +using CashRegister.Internal.Financial; + +namespace CashRegister.Internal.Calculation +{ + /// + /// Transaction Class + /// + public class Transaction + { + private decimal cost; + private decimal tendered; + private DataViolations violation; + private string RawInput { get; } + public List Change { get; private set; } + + /// + /// Initializes a new instance of the class. + /// + /// The input. + public Transaction(string input) + { + RawInput = input; + Change = new List(); + ValidateInput(); + } + + /// + /// Validates the input. + /// + private void ValidateInput() + { + if (RawInput == null) //checking for null + { + violation = DataViolations.EmptyInput; + } + else + { + string[] split = RawInput.Split(','); + + if (split.Length != 2) //checking for input count violations + { + violation = split.Length < 2 ? DataViolations.TooFewInputs : DataViolations.TooManyInputs; + } + else if (!decimal.TryParse(split[0], out cost) || !decimal.TryParse(split[1], out tendered)) //checking for non numbers + { + violation = DataViolations.NonNumeric; + } + else if (cost < 0) //checking for negative cost input + { + violation = DataViolations.CostNegative; + } + else if (tendered < 0) //checking for negative tendered input + { + violation = DataViolations.TenderedNegative; + } + else if (cost > tendered) //checking for insufficient tendered funds + { + violation = DataViolations.InsufficentTendered; + } + else if(cost > (int.MaxValue) || tendered > (int.MaxValue)) + { + violation = DataViolations.ExeceededcalculableLimits; + } + else + { + violation = null; //all clear + } + } + } + + /// + /// Generates the output of change due or various errors + /// If no violations have been tripped - figure out currency denominations for return + /// If change due is divisible by 3 Mod3ChangeGeneration will be called. + /// Otherwise change is given as optimally as possible. + /// + /// The region currency. + /// random seed + /// if set to true [randomize all change]. otherwise randomizes decimal value only + internal void GenerateChange(IRegionCurrency regionCurrency, Random r, bool randomizeAllChange) + { + if (violation == null) + { + Change.Clear(); + decimal changeValue = Math.Round(tendered - cost, 2, MidpointRounding.AwayFromZero); + string[] stringSplit = changeValue.ToString().Split('.'); + int x = stringSplit.Length > 1 ? int.Parse(stringSplit[1]) : 0; + if (x % 3 == 0) + { + //wasn't sure if all money due back should be subject to random behavior + //or just the decimal value - I implemented both; + if (randomizeAllChange) + { + Change = Mod3ChangeGeneration(changeValue, regionCurrency, r); + } + else//randomizes decimal value only. + { + Change = OptimizedChangeReturned(decimal.Parse(stringSplit[0]), regionCurrency); + Change.AddRange(Mod3ChangeGeneration(decimal.Parse(string.Format("0.{0}", x)), regionCurrency, r)); + } + } + else + { + Change = OptimizedChangeReturned(changeValue, regionCurrency); + } + } + else + { + Change.Add(string.Format("Error with input ({0}):", RawInput)); + Change.Add(violation.Value); + } + } + + /// + /// Optimizes the change returned. + /// Gives the most efficient change back possible. + /// + /// The change value. + /// The region currency. + /// + private List OptimizedChangeReturned(decimal changeValue, IRegionCurrency regionCurrency) + { + List toReturn = new List(); + foreach (var c in regionCurrency.GetDescendingOrderedCurrencies()) + { + if (c.Value <= changeValue) + { + int times = (int)(changeValue / c.Value); + toReturn.Add(string.Format("{0} {1}", times, times > 1 ? c.PluralName : c.Name)); + changeValue -= (times * c.Value); + } + } + return toReturn; + } + + /// + /// If the change due is divisible by 3, give random denominations + /// these random denominations must equal the appropriate total amount + /// of change due + /// + /// The change value. + /// The region currency. + /// The random seed + /// + private List Mod3ChangeGeneration(decimal changeValue, IRegionCurrency regionCurrency, Random r) + { + Dictionary changeDue = new Dictionary(); + List toReturn = new List(); + foreach (Currency c in regionCurrency.Denominations) + { + changeDue.Add(c, 0); + } + while (changeValue > 0) + { + Currency tmp = regionCurrency.Denominations[r.Next(regionCurrency.Denominations.Count)]; + + if (changeValue >= tmp.Value) + { + try + { + int maxNumberOfTimes = (int)(changeValue / tmp.Value) + 1; //adding 1 because random max is exclusive + int actualTimes = r.Next(maxNumberOfTimes); + changeDue[tmp] += actualTimes; + changeValue -= (actualTimes * tmp.Value); + } + catch (OverflowException) + { + //eat the overflow exception and try again + //this happens when the initial change amount due is at or near the limit for Int32 + //and we try to generate change using something less than 1 ex: a nickel. + //allow the system to try larger denominations to reduce the total change due being tracked + } + + } + } + //populate the change denominations to return + foreach (Currency c in regionCurrency.GetDescendingOrderedCurrencies()) + { + if (changeDue[c] > 0) + { + toReturn.Add(string.Format("{0} {1}", changeDue[c], changeDue[c] > 1 ? c.PluralName : c.Name)); + } + } + return toReturn; + } + + /// + /// Overrides ToString() + /// + /// + /// A that represents this instance. + /// + public override string ToString() + { + return string.Join(", ", Change); + } + } +} \ No newline at end of file diff --git a/CashRegister/Internal/Calculation/TransactionProcessor.cs b/CashRegister/Internal/Calculation/TransactionProcessor.cs new file mode 100644 index 00000000..15ed26e7 --- /dev/null +++ b/CashRegister/Internal/Calculation/TransactionProcessor.cs @@ -0,0 +1,112 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using CashRegister.Internal.Financial; + +namespace CashRegister.Internal.Calculation +{ + /// + /// Houses the main function for reading, processing and writing output + /// + internal static class TransactionProcessor + { + /// + /// Processes the transactions. + /// + /// The input file. + /// The output file. + /// The data dump threshold. + /// if set to true [randomize all change]. + /// The region currency. + /// + public static bool ProcessTransactions(string inputFile, string outputFile, int dataDumpThreshold, bool randomizeAllChange, IRegionCurrency regionCurrency) + { + if (regionCurrency == null) + { + return false; + } + //book-keeping + int start = 0; + int dataDumpOffset = 0; + //number of lines to read at once + int take = 50000; + Mutex _m = new Mutex(); + Dictionary transactions = new Dictionary(); + while (true) + { + string[] v = null; + try + { + v = File.ReadLines(inputFile).Skip(start).Take(take).ToArray(); + } + catch (Exception e) + { + Console.WriteLine(e.Message); + return false; + } + //using Parallel.ForEach for increased performance when calculating change + Parallel.ForEach(v, (s, x, i) => + { + Transaction tmp = new Transaction(s); + tmp.GenerateChange(regionCurrency, new Random(Guid.NewGuid().GetHashCode()), randomizeAllChange); + int index = (int)i + start; + _m.WaitOne(); + transactions.Add(index, tmp); + _m.ReleaseMutex(); + }); + //section to write transaction data to disk - preventing out of memory issues + if (transactions.Count % dataDumpThreshold == 0) + { + try + { + using (StreamWriter writer = new StreamWriter(outputFile, append: true)) + { + int offset = dataDumpThreshold * dataDumpOffset; + for (int i = 0; i < transactions.Count; i++) + { + writer.WriteLine(transactions[i + offset].ToString()); + } + } + } + catch (Exception e) + { + Console.WriteLine(e.Message); + return false; + } + transactions.Clear(); + dataDumpOffset++; + } + start += take; + if (v.Length < take) + { + break; + } + } + if (transactions.Count > 0)//write the remaining transactions + { + try + { + using (StreamWriter writer = new StreamWriter(outputFile, append: true)) + { + int offset = dataDumpThreshold * dataDumpOffset; + for (int i = 0; i < transactions.Count; i++) + { + writer.WriteLine(transactions[i + offset].ToString()); + } + } + } + catch (Exception e) + { + Console.WriteLine(e.Message); + return false; + } + } + return true; + } + + } +} diff --git a/CashRegister/Internal/Financial/Currency.cs b/CashRegister/Internal/Financial/Currency.cs new file mode 100644 index 00000000..e1bb4cfb --- /dev/null +++ b/CashRegister/Internal/Financial/Currency.cs @@ -0,0 +1,16 @@ +namespace CashRegister.Internal.Financial +{ + internal class Currency + { + public string Name { get; } + public string PluralName { get; } + public decimal Value { get; } + + public Currency(string name, string plural, decimal value) + { + Name = name; + PluralName = plural; + Value = value; + } + } +} \ No newline at end of file diff --git a/CashRegister/Internal/Financial/IRegionCurrency.cs b/CashRegister/Internal/Financial/IRegionCurrency.cs new file mode 100644 index 00000000..681241cc --- /dev/null +++ b/CashRegister/Internal/Financial/IRegionCurrency.cs @@ -0,0 +1,16 @@ +using System.Collections.Generic; + +namespace CashRegister.Internal.Financial +{ + /// Interface for Regional Currencies + internal interface IRegionCurrency + { + List Denominations { get; } + + /// + /// Gets the descending ordered currencies. + /// + /// + List GetDescendingOrderedCurrencies(); + } +} \ No newline at end of file diff --git a/CashRegister/Internal/Financial/RegionCurrency/CADCurrency.cs b/CashRegister/Internal/Financial/RegionCurrency/CADCurrency.cs new file mode 100644 index 00000000..665c862f --- /dev/null +++ b/CashRegister/Internal/Financial/RegionCurrency/CADCurrency.cs @@ -0,0 +1,38 @@ +using System.Collections.Generic; +using System.Linq; + +namespace CashRegister.Internal.Financial.RegionCurrency +{ + /// + /// Canadian Dollar implementation of currency + /// + /// + internal class CADCurrency : IRegionCurrency + { + public List Denominations { get; } + + public CADCurrency() + { + Denominations = new List + { + new Currency("penny", "pennies", .01m), + new Currency("nickel", "nickels", .05m), + new Currency("dime", "dimes", .10m), + new Currency("quarter", "quarters", .25m), + new Currency("half dollar", "half dollars", .50m), + new Currency("loonie", "loonies", 1), + new Currency("toonie", "toonies", 2), + new Currency("five", "fives", 5), + new Currency("ten", "tens", 10), + new Currency("twenty", "twenties", 20), + new Currency("fifty", "fifties", 50), + new Currency("hundred", "hundreds", 100) + }; + } + + public List GetDescendingOrderedCurrencies() + { + return Denominations.OrderByDescending(x => x.Value).ToList(); + } + } +} \ No newline at end of file diff --git a/CashRegister/Internal/Financial/RegionCurrency/EURCurrency.cs b/CashRegister/Internal/Financial/RegionCurrency/EURCurrency.cs new file mode 100644 index 00000000..99abc487 --- /dev/null +++ b/CashRegister/Internal/Financial/RegionCurrency/EURCurrency.cs @@ -0,0 +1,40 @@ +using System.Collections.Generic; +using System.Linq; + +namespace CashRegister.Internal.Financial.RegionCurrency +{ + /// + /// Euro implementation of currency + /// + /// + internal class EURCurrency : IRegionCurrency + { + public List Denominations { get; } + + public EURCurrency() + { + Denominations = new List + { + new Currency("1c", "1c", .01m), + new Currency("2c", "2c", .02m), + new Currency("5c", "5c", .05m), + new Currency("10c", "10c", .10m), + new Currency("20c", "20c", .20m), + new Currency("50c", "50c", .50m), + new Currency("one", "ones", 1), + new Currency("two", "twos", 2), + new Currency("five", "fives", 5), + new Currency("ten", "tens", 10), + new Currency("twenty", "twenties", 20), + new Currency("hundred", "hundreds", 100), + new Currency("two hundred", "two hundreds", 200), + new Currency("five hundred", "five hundreds", 500) + }; + } + + public List GetDescendingOrderedCurrencies() + { + return Denominations.OrderByDescending(x => x.Value).ToList(); + } + } +} \ No newline at end of file diff --git a/CashRegister/Internal/Financial/RegionCurrency/USDCurrency.cs b/CashRegister/Internal/Financial/RegionCurrency/USDCurrency.cs new file mode 100644 index 00000000..74b87b07 --- /dev/null +++ b/CashRegister/Internal/Financial/RegionCurrency/USDCurrency.cs @@ -0,0 +1,36 @@ +using System.Collections.Generic; +using System.Linq; + +namespace CashRegister.Internal.Financial.RegionCurrency +{ + /// + /// US Dollar implementation + /// + /// + internal class USDCurrency : IRegionCurrency + { + public List Denominations { get; } + + public USDCurrency() + { + Denominations = new List + { + new Currency("penny", "pennies", .01m), + new Currency("nickel", "nickels", .05m), + new Currency("dime", "dimes", .10m), + new Currency("quarter", "quarters", .25m), + new Currency("one", "ones", 1), + new Currency("five", "fives", 5), + new Currency("ten", "tens", 10), + new Currency("twenty", "twenties", 20), + new Currency("fifty", "fifties", 50), + new Currency("hundred", "hundreds", 100) + }; + } + + public List GetDescendingOrderedCurrencies() + { + return Denominations.OrderByDescending(x => x.Value).ToList(); + } + } +} \ No newline at end of file diff --git a/CashRegister/Internal/Helpers/CurrencySelector.cs b/CashRegister/Internal/Helpers/CurrencySelector.cs new file mode 100644 index 00000000..5d2ee909 --- /dev/null +++ b/CashRegister/Internal/Helpers/CurrencySelector.cs @@ -0,0 +1,45 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using CashRegister.Internal.Financial; +using CashRegister.Internal.Financial.RegionCurrency; + +namespace CashRegister.Internal.Helpers +{ + /// + /// Simple helper for setting the currency that will be used + /// moves it out of Program + /// + internal static class CurrencySelector + { + public static IRegionCurrency GetCurrency() + { + return GetCurrency(new RegionInfo(System.Threading.Thread.CurrentThread.CurrentUICulture.LCID).ISOCurrencySymbol); + } + + public static IRegionCurrency GetCurrency(string toUse) + { + if (string.IsNullOrEmpty(toUse)) + { + toUse = new RegionInfo(System.Threading.Thread.CurrentThread.CurrentUICulture.LCID).ISOCurrencySymbol; + } + switch (toUse) + { + case "USD": + return new USDCurrency(); + case "CAD": + return new CADCurrency(); + case "EUR": + return new EURCurrency(); + + default: + break; + } + Console.WriteLine(string.Format("{0}: is unsupported currency ISOCode", toUse)); + return null; + } + } +} diff --git a/CashRegister/Internal/Helpers/TestFileGeneration.cs b/CashRegister/Internal/Helpers/TestFileGeneration.cs new file mode 100644 index 00000000..aad1f74d --- /dev/null +++ b/CashRegister/Internal/Helpers/TestFileGeneration.cs @@ -0,0 +1,36 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace CashRegister.Internal.Helpers +{ + /// + /// Helper I used to generate + /// + static class TestFileGeneration + { + public static bool GenerateTestFile(string fileName, int lines) + { + string[] badValues = { "", "x,y", "null", "1.23 4.56", "7.89", "NaN,NaN", "null", null, string.Format("5.11, {0}", double.MaxValue)}; + Random r = new Random(); + using (StreamWriter writer = new StreamWriter(fileName)) + { + for (int i = 0; i < lines; i++) + { + if (r.Next(0, 100) % 5 == 0) + { + writer.WriteLine(badValues[r.Next(0, badValues.Length - 1)]); + } + else + { + writer.WriteLine(string.Format("{0:0.00},{1:0.00}", Math.Round(r.Next(0, 10) + r.NextDouble(), 2, MidpointRounding.AwayFromZero), Math.Round(r.Next(10, 20) + r.NextDouble(), 2, MidpointRounding.AwayFromZero))); + } + } + } + return File.Exists(fileName); + } + } +} diff --git a/CashRegister/Program.cs b/CashRegister/Program.cs new file mode 100644 index 00000000..de88dd52 --- /dev/null +++ b/CashRegister/Program.cs @@ -0,0 +1,79 @@ +using System; +using System.Collections.Generic; +using System.IO; +using CashRegister.Internal.Calculation; +using CashRegister.Internal.Helpers; +using CommandLine; + +namespace CashRegister +{ + internal class Program + { + /// + /// Command line parser options; + /// + public class Options + { + [Option('i', "inputFile", Required = true, HelpText = "File to be processed.")] + public string InputFile { get; set; } + + [Option('o', "outputFile", Required = false, HelpText = "File to write to.")] + public string OutputFile { get; set; } + + [Option('r', "randomizeAllChange", Required = false, HelpText = "True: Randomize all change due. False(default): randomize only decimal amount.")] + public bool RandomizeAllChange { get; set; } + + [Option('g', "generateInputFile", Required = false, HelpText = "Will generate an input file using inputFile name provided and populate with test data. Default number of rows is 500, set different value with n switch")] + public bool GenerateInputFile { get; set; } + [Option('n', "numberOfRowsToGenerate", Required = false, HelpText = "Overrides the number of rows created when generating test file")] + public int RowsToGenerate { get; set; } + + [Option('c', "currencyISOCode", Required = false, HelpText = "Used to override local currency value for processing.")] + public string CurrencyCode { get; set; } + } + private static void Main(string[] args) + { + Parser.Default.ParseArguments(args).WithParsed(RunOptions).WithNotParsed(ParseError); + + } + + private static void ParseError(IEnumerable obj) + { + Console.WriteLine("Invalid command line options given"); + } + + private static void RunOptions(Options obj) + { + string inputFile = obj.InputFile; + string outputFile = obj.OutputFile; + if (outputFile == null) + { + outputFile = Path.GetDirectoryName(inputFile) + @"\" + Path.GetFileNameWithoutExtension(inputFile) + "_processed.txt"; + } + + if (obj.GenerateInputFile) + { + int rowsToGenerate = obj.RowsToGenerate == 0 ? 500 : obj.RowsToGenerate; + TestFileGeneration.GenerateTestFile(inputFile, rowsToGenerate); + } + + + //threshold for storing transactions in memory + //can be much higher but 100k seemed reasonable + //for this exercise. + int dataDumpThreshold = 100000; + + + if (TransactionProcessor.ProcessTransactions(inputFile, outputFile, dataDumpThreshold, obj.RandomizeAllChange, CurrencySelector.GetCurrency(obj.CurrencyCode))) + { + Console.WriteLine("Finished processing without error"); + } + else + { + Console.WriteLine("Finished processing with error(s)"); + } + Console.WriteLine("Press any key to end..."); + Console.ReadKey(); + } + } +} \ No newline at end of file diff --git a/CashRegister/Properties/AssemblyInfo.cs b/CashRegister/Properties/AssemblyInfo.cs new file mode 100644 index 00000000..6bd303b5 --- /dev/null +++ b/CashRegister/Properties/AssemblyInfo.cs @@ -0,0 +1,37 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("CashRegister")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("CashRegister")] +[assembly: AssemblyCopyright("Copyright © 2020")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] +[assembly: InternalsVisibleTo("CashRegisterTests")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("04f488d6-ca12-4dfb-98f8-6242a6cb1668")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] \ No newline at end of file diff --git a/CashRegister/ReadMe.txt b/CashRegister/ReadMe.txt new file mode 100644 index 00000000..5f282702 --- /dev/null +++ b/CashRegister/ReadMe.txt @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/CashRegister/packages.config b/CashRegister/packages.config new file mode 100644 index 00000000..ecd5a089 --- /dev/null +++ b/CashRegister/packages.config @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/CashRegisterSolution.sln b/CashRegisterSolution.sln new file mode 100644 index 00000000..55e2556c --- /dev/null +++ b/CashRegisterSolution.sln @@ -0,0 +1,31 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.29806.167 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CashRegisterSolution", "CashRegister\CashRegisterSolution.csproj", "{04F488D6-CA12-4DFB-98F8-6242A6CB1668}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CashRegisterTests", "CashRegisterTests\CashRegisterTests.csproj", "{807B6F2B-2397-45B6-AF56-BB5F5D80CF21}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {04F488D6-CA12-4DFB-98F8-6242A6CB1668}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {04F488D6-CA12-4DFB-98F8-6242A6CB1668}.Debug|Any CPU.Build.0 = Debug|Any CPU + {04F488D6-CA12-4DFB-98F8-6242A6CB1668}.Release|Any CPU.ActiveCfg = Release|Any CPU + {04F488D6-CA12-4DFB-98F8-6242A6CB1668}.Release|Any CPU.Build.0 = Release|Any CPU + {807B6F2B-2397-45B6-AF56-BB5F5D80CF21}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {807B6F2B-2397-45B6-AF56-BB5F5D80CF21}.Debug|Any CPU.Build.0 = Debug|Any CPU + {807B6F2B-2397-45B6-AF56-BB5F5D80CF21}.Release|Any CPU.ActiveCfg = Release|Any CPU + {807B6F2B-2397-45B6-AF56-BB5F5D80CF21}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {695A6EA6-4672-450A-80CA-B91FFB4DF250} + EndGlobalSection +EndGlobal diff --git a/CashRegisterTests/CashRegisterTests.csproj b/CashRegisterTests/CashRegisterTests.csproj new file mode 100644 index 00000000..54fcbea4 --- /dev/null +++ b/CashRegisterTests/CashRegisterTests.csproj @@ -0,0 +1,107 @@ + + + + Debug + AnyCPU + {807B6F2B-2397-45B6-AF56-BB5F5D80CF21} + Library + Properties + CashRegisterTests + CashRegisterTests + v4.7.2 + 512 + {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + 10.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + $(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages + False + UnitTest + + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + ..\packages\Castle.Core.4.4.0\lib\net45\Castle.Core.dll + + + ..\packages\Moq.4.13.1\lib\net45\Moq.dll + + + + + ..\packages\System.Runtime.CompilerServices.Unsafe.4.5.0\lib\netstandard2.0\System.Runtime.CompilerServices.Unsafe.dll + + + ..\packages\System.Threading.Tasks.Extensions.4.5.1\lib\netstandard2.0\System.Threading.Tasks.Extensions.dll + + + + + + + + + + + + + + + + + + + + + {04F488D6-CA12-4DFB-98F8-6242A6CB1668} + CashRegisterSolution + + + + + + + + + + + False + + + False + + + False + + + False + + + + + + + + \ No newline at end of file diff --git a/CashRegisterTests/Internal/Calculation/TransactionTests.cs b/CashRegisterTests/Internal/Calculation/TransactionTests.cs new file mode 100644 index 00000000..e0be8552 --- /dev/null +++ b/CashRegisterTests/Internal/Calculation/TransactionTests.cs @@ -0,0 +1,241 @@ +using System; +using System.Text.RegularExpressions; +using CashRegister.Internal.Financial; +using CashRegister.Internal.Financial.RegionCurrency; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Moq; + +namespace CashRegister.Internal.Calculation.Tests +{ + [TestClass()] + public class TransactionTests + { + [TestMethod()] + public void Test_Input_Line_Contains_Too_Many_Items() + { + Transaction t = new Transaction("1.23,4.56,7.89"); + t.GenerateChange(null, null, true); + Assert.IsTrue(t.Change.Contains(DataViolations.TooManyInputs.Value)); + } + + [TestMethod()] + public void Test_Input_Line_Contains_Too_Few_Items() + { + Transaction t = new Transaction("1.23"); + t.GenerateChange(null, null, true); + Assert.IsTrue(t.Change.Contains(DataViolations.TooFewInputs.Value)); + } + + [TestMethod()] + public void Test_Input_Line_Is_Empty() + { + Transaction t = new Transaction(""); + t.GenerateChange(null, null, true); + Assert.IsTrue(t.Change.Contains(DataViolations.TooFewInputs.Value)); + } + + [TestMethod()] + public void Test_Input_Line_Is_Null() + { + Transaction t = new Transaction(null); + t.GenerateChange(null, null, true); + Assert.IsTrue(t.Change.Contains(DataViolations.EmptyInput.Value)); + } + + [TestMethod()] + public void Test_Input_Line_Is_NaN() + { + Transaction t = new Transaction("NaN,NaN"); + t.GenerateChange(null, null, true); + Assert.IsTrue(t.Change.Contains(DataViolations.NonNumeric.Value)); + } + + [TestMethod()] + public void Test_Input_Line_Is_NonNumeric() + { + Transaction t = new Transaction("a,b"); + t.GenerateChange(null, null, true); + Assert.IsTrue(t.Change.Contains(DataViolations.NonNumeric.Value)); + } + + [TestMethod()] + public void Test_Input_Line_Contains_Negative_Cost() + { + Transaction t = new Transaction("-1.0,1"); + t.GenerateChange(null, null, true); + Assert.IsTrue(t.Change.Contains(DataViolations.CostNegative.Value)); + } + + [TestMethod()] + public void Test_Input_Line_Contains_Negative_Tendered() + { + Transaction t = new Transaction("1.11,-1.11"); + t.GenerateChange(null, null, true); + Assert.IsTrue(t.Change.Contains(DataViolations.TenderedNegative.Value)); + } + + [TestMethod()] + public void Test_Input_Line_Tendered_Value_Is_Lower_Than_Cost() + { + Transaction t = new Transaction("5,2"); + t.GenerateChange(null, null, true); + Assert.AreEqual(true, t.Change.Contains(DataViolations.InsufficentTendered.Value)); + } + + [TestMethod()] + public void Test_Input_Line_Upper_Bounds() + { + decimal d = decimal.MaxValue; + Transaction t = new Transaction(string.Format("100,{0}", d)); + Random r = new Random(1); + IRegionCurrency test = new USDCurrency(); + t.GenerateChange(test, r, true); + Assert.IsTrue(t.Change.Contains(DataViolations.ExeceededcalculableLimits.Value)); + } + + [TestMethod()] + public void Test_Input_Line_Upper_Bound_Less_One() + { + int i = (int.MaxValue) - 1; + Transaction t = new Transaction(string.Format("100,{0}", i)); + Random r = new Random(1); + IRegionCurrency test = new USDCurrency(); + t.GenerateChange(test, r, true); + decimal calculatedChange = 0m; + foreach (string s in t.Change) + { + foreach (Currency c in test.Denominations) + { + if (s.Contains(c.Name) || s.Contains(c.PluralName)) + { + string[] numbers = Regex.Split(s, @"\D+"); + if (!string.IsNullOrEmpty(numbers[0])) + { + calculatedChange += (decimal)(c.Value * int.Parse(numbers[0])); + } + } + } + } + Assert.AreEqual(2147483546m, calculatedChange); + } + + [TestMethod()] + public void Test_Input_Line_Beyond_Upper_Bounds() + { + double d = double.MaxValue; + Transaction t = new Transaction(string.Format("10,{0}", d)); + t.GenerateChange(null, null, true); + Assert.IsTrue(t.Change.Contains(DataViolations.NonNumeric.Value)); + } + + [TestMethod()] + public void Test_Input_Line_Lower_Bounds() + { + decimal d = decimal.MinValue; + Transaction t = new Transaction(string.Format("{0},10", d)); + t.GenerateChange(null, null, true); + Assert.IsTrue(t.Change.Contains(DataViolations.CostNegative.Value)); + } + + [TestMethod()] + public void Test_Input_Line_Valid_Input() + { + Transaction t = new Transaction("3,5"); + Assert.AreEqual(0, t.Change.Count); + } + + [TestMethod()] + public void Test_Input_Line_Valid_Input_Result_USD() + { + Transaction t = new Transaction("13.59,200"); + IRegionCurrency test = new USDCurrency(); + t.GenerateChange(test, null, true); + string expected = "1 hundred, 1 fifty, 1 twenty, 1 ten, 1 five, 1 one, 1 quarter, 1 dime, 1 nickel, 1 penny"; + Assert.AreEqual(expected, t.ToString()); + } + + [TestMethod()] + public void Test_Input_Line_Valid_Input_Result_EUR() + { + Transaction t = new Transaction("61.12,900"); + IRegionCurrency test = new EURCurrency(); + t.GenerateChange(test, null, true); + string expected = "1 five hundred, 1 two hundred, 1 hundred, 1 twenty, 1 ten, 1 five, 1 two, 1 one, 1 50c, 1 20c, 1 10c, 1 5c, 1 2c, 1 1c"; + Assert.AreEqual(expected, t.ToString()); + } + + [TestMethod()] + public void Test_Input_Line_Valid_Input_Result_CAD() + { + Transaction t = new Transaction("11.09,200"); + IRegionCurrency test = new CADCurrency(); + t.GenerateChange(test, null, true); + string expected = "1 hundred, 1 fifty, 1 twenty, 1 ten, 1 five, 1 toonie, 1 loonie, 1 half dollar, 1 quarter, 1 dime, 1 nickel, 1 penny"; + Assert.AreEqual(expected, t.ToString()); + } + + [TestMethod()] + public void Test_Input_Line_Valid_Input_Random_Change_Result() + { + Random r = new Random(99); + Transaction t = new Transaction("5.01, 20"); + IRegionCurrency test = new USDCurrency(); + t.GenerateChange(test, r, false); + string seed99 = t.ToString(); + r = new Random(1);//reset random seed + t.GenerateChange(test, r, false); + Assert.AreNotEqual(seed99, t.ToString()); + } + + [TestMethod()] + public void Test_Input_Line_Valid_Input_Random_Full_Result() + { + Random r = new Random(99); + Transaction t = new Transaction("5.01, 20"); + IRegionCurrency test = new USDCurrency(); + t.GenerateChange(test, r, true); + string seed99 = t.ToString(); + r = new Random(1);//reset random seed + t.GenerateChange(test, r, false); + Assert.AreNotEqual(seed99, t.ToString()); + } + + [TestMethod()] + public void Test_Input_Line_Valid_Input_Random_Comparisons() + { + Random r = new Random(1); + Random r2 = new Random(2000); + Transaction t = new Transaction("5.97, 100"); + Transaction t2 = new Transaction("5.97, 100"); + IRegionCurrency test = new USDCurrency(); + t.GenerateChange(test, r, true); + t2.GenerateChange(test, r2, true); + Assert.AreNotEqual(t2.ToString(), t.ToString()); + } + + [TestMethod()] + public void Test_Input_Line_Valid_Input_Random_Change_Correctness() + { + Random r = new Random(); + Transaction t = new Transaction("5.97, 10"); + IRegionCurrency test = new USDCurrency(); + t.GenerateChange(test, r, true); + decimal calculatedChange = 0m; + foreach (string s in t.Change) + { + foreach (Currency c in test.Denominations) + { + if (s.Contains(c.Name) || s.Contains(c.PluralName)) + { + string[] numbers = Regex.Split(s, @"\D+"); + if (!string.IsNullOrEmpty(numbers[0])) + { + calculatedChange += (decimal)(c.Value * int.Parse(numbers[0])); + } + } + } + } + Assert.AreEqual(4.03m, calculatedChange); + } + } +} \ No newline at end of file diff --git a/CashRegisterTests/Properties/AssemblyInfo.cs b/CashRegisterTests/Properties/AssemblyInfo.cs new file mode 100644 index 00000000..de0bc78f --- /dev/null +++ b/CashRegisterTests/Properties/AssemblyInfo.cs @@ -0,0 +1,35 @@ +using System.Reflection; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("CashRegisterTests")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("CashRegisterTests")] +[assembly: AssemblyCopyright("Copyright © 2020")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("807b6f2b-2397-45b6-af56-bb5f5d80cf21")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] \ No newline at end of file diff --git a/CashRegisterTests/app.config b/CashRegisterTests/app.config new file mode 100644 index 00000000..a0807c2c --- /dev/null +++ b/CashRegisterTests/app.config @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/CashRegisterTests/packages.config b/CashRegisterTests/packages.config new file mode 100644 index 00000000..f35aa412 --- /dev/null +++ b/CashRegisterTests/packages.config @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file