From 799d15ca7a369b8366b59b9039a680ad4a8526cb Mon Sep 17 00:00:00 2001 From: Richard Todosichuk Date: Fri, 20 Dec 2019 17:41:45 -0500 Subject: [PATCH] Cash Register Solution --- .gitignore | 356 ++++++++++++++++++ README.md | 63 ++++ Source/CashRegister/CashRegister.sln | 47 +++ .../CashRegister/CashRegisterApp/App.config | 6 + .../CashRegisterApp/CashRegisterApp.csproj | 59 +++ .../CashRegister/CashRegisterApp/Program.cs | 100 +++++ .../Properties/AssemblyInfo.cs | 35 ++ .../CashRegisterLib.Tests.csproj | 74 ++++ .../CashRegisterLib.Tests/CashierTests.cs | 235 ++++++++++++ .../Properties/AssemblyInfo.cs | 19 + .../CashRegisterLib.Tests/packages.config | 5 + .../CashRegisterLib/CashRegisterLib.csproj | 48 +++ .../CashRegister/CashRegisterLib/Cashier.cs | 210 +++++++++++ .../Properties/AssemblyInfo.cs | 35 ++ Source/CashRegister/InputFiles/Bad.csv | 13 + .../CashRegister/InputFiles/BiggerValues.csv | 5 + Source/CashRegister/InputFiles/BlankLines.csv | 7 + Source/CashRegister/InputFiles/Empty.csv | 0 Source/CashRegister/InputFiles/Sample1.csv | 3 + Source/CashRegister/InputFiles/ZeroValues.csv | 5 + 20 files changed, 1325 insertions(+) create mode 100644 .gitignore create mode 100644 Source/CashRegister/CashRegister.sln create mode 100644 Source/CashRegister/CashRegisterApp/App.config create mode 100644 Source/CashRegister/CashRegisterApp/CashRegisterApp.csproj create mode 100644 Source/CashRegister/CashRegisterApp/Program.cs create mode 100644 Source/CashRegister/CashRegisterApp/Properties/AssemblyInfo.cs create mode 100644 Source/CashRegister/CashRegisterLib.Tests/CashRegisterLib.Tests.csproj create mode 100644 Source/CashRegister/CashRegisterLib.Tests/CashierTests.cs create mode 100644 Source/CashRegister/CashRegisterLib.Tests/Properties/AssemblyInfo.cs create mode 100644 Source/CashRegister/CashRegisterLib.Tests/packages.config create mode 100644 Source/CashRegister/CashRegisterLib/CashRegisterLib.csproj create mode 100644 Source/CashRegister/CashRegisterLib/Cashier.cs create mode 100644 Source/CashRegister/CashRegisterLib/Properties/AssemblyInfo.cs create mode 100644 Source/CashRegister/InputFiles/Bad.csv create mode 100644 Source/CashRegister/InputFiles/BiggerValues.csv create mode 100644 Source/CashRegister/InputFiles/BlankLines.csv create mode 100644 Source/CashRegister/InputFiles/Empty.csv create mode 100644 Source/CashRegister/InputFiles/Sample1.csv create mode 100644 Source/CashRegister/InputFiles/ZeroValues.csv diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..0950c7f2 --- /dev/null +++ b/.gitignore @@ -0,0 +1,356 @@ + +# Created by https://www.gitignore.io/api/visualstudio +# Edit at https://www.gitignore.io/?templates=visualstudio + +### VisualStudio ### +## 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 + +# Mono auto generated files +mono_crash.* + +# 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 +nunit-*.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 +# NuGet Symbol Packages +*.snupkg +# 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 +*.appxbundle +*.appxupload + +# 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 +*- [Bb]ackup.rdl +*- [Bb]ackup ([0-9]).rdl +*- [Bb]ackup ([0-9][0-9]).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/ + +# 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 + +# Backup folder for Package Reference Convert tool in Visual Studio 2017 +MigrationBackup/ + +# End of https://www.gitignore.io/api/visualstudio \ No newline at end of file diff --git a/README.md b/README.md index 06a2101e..abd04bc6 100644 --- a/README.md +++ b/README.md @@ -39,3 +39,66 @@ Please use whatever techniques you feel are applicable to solve the problem. We Please fork this repository. If your solution involves code auto generated by a development tool, please commit it separately from your own work. When you have completed your solution, please issue a pull request to notify us that you are ready. Have fun. + +Compiling +--------- +The project was built in Visual Studio 2019 Community. The target framework is `.NET Framework4.7.2` and will only run on Windows. + +1. Open `CashRegister.sln`. +2. Click "Start" in Visual Studio or press (F6). +3. The code should build without errors. + +Projects +-------- +*CashRegisterApp* - This is a console application that holds the main EXE. + +*CashRegisterLib* - This is a DLL that holds the logic to get the change string for a given total amount and paid amount. + +*CashRegisterLib.Test* - This holds MSTest to test the GetChange() in the CashRegisterLib library. + +Running from Visual Studio +-------------------------- + +1. Open `CashRegister.sln` in Visual Studio 2019. +2. Press F5 to run in debug mode. +3. You should see the output for `sample1.csv` file displayed in a console window. + +Running from Command Prompt +-------------------------- +1. Make sure you have built the project. +2. Open a command prompt and change directory into +`\CashRegister\Source\CashRegister\CashRegisterApp\bin\Debug` +3. Run `dir` You should see `CashRegisterApp.exe` listed in the output. +4. Run `CashRegisterApp.exe` passing in a file name which is to be processed as an argument + +```bash +cashRegisterApp.exe ..\..\..\InputFiles\Sample1.csv +``` +5. You should see output like the following: + +```bash +3 quarters,1 dime,3 pennies +3 pennies +1 dollar,2 quarters,2 nickels,7 pennies +``` + +Assumptions +----------- + +I tried a test-driven approach in the creation of this solution. After thinking through the different scenarios, I have made the following assumptions: + +* Blank lines are ignored and are valid in the file. +* Blank total amounts and/or blank paid amounts are invalid. (Ex: `33, `) +* Only the first two comma separated values are used the rest are ignored. +( Ex: `4,5,6`) +* `.01` is invalid. It needs to be `0.01`. +* Negative total amounts and negative paid amounts are invalid. +* If the total is 0, it should fall into the `twist` logic. +* Based on the code that I have written, I set the max change that could be calculated to be `(Int32.MaxValue / 100)`. An exception will be thrown if it is larger. This is to keep it within a calculatable range used in the `twist` logic. + + + + + + + diff --git a/Source/CashRegister/CashRegister.sln b/Source/CashRegister/CashRegister.sln new file mode 100644 index 00000000..b0bada1d --- /dev/null +++ b/Source/CashRegister/CashRegister.sln @@ -0,0 +1,47 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.29609.76 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CashRegisterApp", "CashRegisterApp\CashRegisterApp.csproj", "{030C7C9A-9DEF-47A3-8B44-0AA99640A2CE}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CashRegisterLib", "CashRegisterLib\CashRegisterLib.csproj", "{84DE2951-4E59-4ADA-AFE6-DADAA8833539}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CashRegisterLib.Tests", "CashRegisterLib.Tests\CashRegisterLib.Tests.csproj", "{4FC57344-F83D-4185-879A-0B5295F89221}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "InputFiles", "InputFiles", "{2F70DF5B-A875-42F3-8E6A-22AF06E763B9}" + ProjectSection(SolutionItems) = preProject + InputFiles\Bad.csv = InputFiles\Bad.csv + InputFiles\BiggerValues.csv = InputFiles\BiggerValues.csv + InputFiles\BlankLines.csv = InputFiles\BlankLines.csv + InputFiles\Empty.csv = InputFiles\Empty.csv + InputFiles\Sample1.csv = InputFiles\Sample1.csv + InputFiles\ZeroValues.csv = InputFiles\ZeroValues.csv + EndProjectSection +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {030C7C9A-9DEF-47A3-8B44-0AA99640A2CE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {030C7C9A-9DEF-47A3-8B44-0AA99640A2CE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {030C7C9A-9DEF-47A3-8B44-0AA99640A2CE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {030C7C9A-9DEF-47A3-8B44-0AA99640A2CE}.Release|Any CPU.Build.0 = Release|Any CPU + {84DE2951-4E59-4ADA-AFE6-DADAA8833539}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {84DE2951-4E59-4ADA-AFE6-DADAA8833539}.Debug|Any CPU.Build.0 = Debug|Any CPU + {84DE2951-4E59-4ADA-AFE6-DADAA8833539}.Release|Any CPU.ActiveCfg = Release|Any CPU + {84DE2951-4E59-4ADA-AFE6-DADAA8833539}.Release|Any CPU.Build.0 = Release|Any CPU + {4FC57344-F83D-4185-879A-0B5295F89221}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4FC57344-F83D-4185-879A-0B5295F89221}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4FC57344-F83D-4185-879A-0B5295F89221}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4FC57344-F83D-4185-879A-0B5295F89221}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {E8AC51FD-DF35-413B-8B7B-84B1A06E8FA8} + EndGlobalSection +EndGlobal diff --git a/Source/CashRegister/CashRegisterApp/App.config b/Source/CashRegister/CashRegisterApp/App.config new file mode 100644 index 00000000..56efbc7b --- /dev/null +++ b/Source/CashRegister/CashRegisterApp/App.config @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/Source/CashRegister/CashRegisterApp/CashRegisterApp.csproj b/Source/CashRegister/CashRegisterApp/CashRegisterApp.csproj new file mode 100644 index 00000000..9f0d0526 --- /dev/null +++ b/Source/CashRegister/CashRegisterApp/CashRegisterApp.csproj @@ -0,0 +1,59 @@ + + + + + Debug + AnyCPU + {030C7C9A-9DEF-47A3-8B44-0AA99640A2CE} + Exe + CashRegisterApp + CashRegisterApp + v4.7.2 + 512 + true + true + + + AnyCPU + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + AnyCPU + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + + + + + + + + + + + + + + {84de2951-4e59-4ada-afe6-dadaa8833539} + CashRegisterLib + + + + \ No newline at end of file diff --git a/Source/CashRegister/CashRegisterApp/Program.cs b/Source/CashRegister/CashRegisterApp/Program.cs new file mode 100644 index 00000000..d802d305 --- /dev/null +++ b/Source/CashRegister/CashRegisterApp/Program.cs @@ -0,0 +1,100 @@ +using CashRegisterLib; +using System; +using System.IO; + +namespace CashRegisterApp +{ + class Program + { + static int Main(string[] args) + { + // read args and validate file exists + if (args.Length < 1) + { + Console.WriteLine($"Usage: {System.AppDomain.CurrentDomain.FriendlyName} CSVFileName"); + return -1; + } + + var fileName = args[0]; + if (!File.Exists(fileName)) + { + Console.WriteLine($"ERROR: File not found. \"{fileName}\""); + return - 1; + } + + try + { + // read file validate content and process change + using (var file = new StreamReader(fileName)) + { + string line; + int lineCnt = 0; + var cashier = new Cashier(); + while ((line = file.ReadLine()) != null) + { + lineCnt++; + + if (!string.IsNullOrWhiteSpace(line)) // skip blank lines + { + var amounts = line.Split(','); + if (amounts.Length < 2) + { + Console.WriteLine($"ERROR [Line: {lineCnt}]: File should contain two values separated by a comma."); + break; + } + + var first = amounts[0].Trim(); + var second = amounts[1].Trim(); + + decimal total; + if (!decimal.TryParse(first, out total)) + { + Console.WriteLine($"ERROR [Line: {lineCnt}]: \"{first}\" is not a numeric value."); + return -1; + } + + decimal paid; + if (!decimal.TryParse(second, out paid)) + { + Console.WriteLine($"ERROR [Line: {lineCnt}]: \"{second}\" is not a numeric value."); + return -1; + } + + if (total < 0) + { + Console.WriteLine($"ERROR [Line: {lineCnt}]: Total amount should be a positive number."); + return -1; + } + + if (total < 0) + { + Console.WriteLine($"ERROR [Line: {lineCnt}]: Paid amount should be a positive number."); + return -1; + } + + if (total > paid) + { + Console.WriteLine($"ERROR [Line: {lineCnt}]: Paid amount is less than the total amount."); + return -1; + } + + var change = cashier.GetChange(total, paid); + Console.WriteLine(change); + } + } + } + } + catch (Exception ex) + { + Console.WriteLine($"ERROR: {ex.Message}"); + return -1; + } + + // Pause the console when debugging + if (System.Diagnostics.Debugger.IsAttached) + Console.ReadLine(); + + return 0; + } + } +} diff --git a/Source/CashRegister/CashRegisterApp/Properties/AssemblyInfo.cs b/Source/CashRegister/CashRegisterApp/Properties/AssemblyInfo.cs new file mode 100644 index 00000000..4061a893 --- /dev/null +++ b/Source/CashRegister/CashRegisterApp/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("CashRegisterApp")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("Creative Cash Draw Solutions")] +[assembly: AssemblyProduct("CashRegisterApp")] +[assembly: AssemblyCopyright("Copyright © 2019 Richard Todosichuk")] +[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("030c7c9a-9def-47a3-8b44-0aa99640a2ce")] + +// 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")] diff --git a/Source/CashRegister/CashRegisterLib.Tests/CashRegisterLib.Tests.csproj b/Source/CashRegister/CashRegisterLib.Tests/CashRegisterLib.Tests.csproj new file mode 100644 index 00000000..67eb7aca --- /dev/null +++ b/Source/CashRegister/CashRegisterLib.Tests/CashRegisterLib.Tests.csproj @@ -0,0 +1,74 @@ + + + + + + Debug + AnyCPU + {4FC57344-F83D-4185-879A-0B5295F89221} + Library + Properties + CashRegisterLib.Tests + CashRegisterLib.Tests + v4.7.2 + 512 + {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + 15.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\MSTest.TestFramework.1.3.2\lib\net45\Microsoft.VisualStudio.TestPlatform.TestFramework.dll + + + ..\packages\MSTest.TestFramework.1.3.2\lib\net45\Microsoft.VisualStudio.TestPlatform.TestFramework.Extensions.dll + + + + + + + + + + + + + + {84de2951-4e59-4ada-afe6-dadaa8833539} + CashRegisterLib + + + + + + + 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/Source/CashRegister/CashRegisterLib.Tests/CashierTests.cs b/Source/CashRegister/CashRegisterLib.Tests/CashierTests.cs new file mode 100644 index 00000000..ab46e533 --- /dev/null +++ b/Source/CashRegister/CashRegisterLib.Tests/CashierTests.cs @@ -0,0 +1,235 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using System; + +namespace CashRegisterLib.Tests +{ + [TestClass] + public class CashierTests + { + private Cashier cashier; + + [TestInitialize] + public void SetUp() + { + cashier = new Cashier(); + } + + [TestMethod] + public void WhenPaidEqualsTotal() + { + var results = cashier.GetChange(1.56m, 1.56m); + Assert.AreEqual("Exact change. Nothing to be returned.", results); + } + + [TestMethod] + public void WhenPaidIsLessThanTotal() + { + try + { + cashier.GetChange(2, 1); + } + catch (Exception ex) + { + Assert.IsInstanceOfType(ex, typeof(Exception)); + Assert.AreEqual("Please pay more. Paid amount is less than the total amount.", ex.Message); + return; + } + + Assert.Fail("Exception was expected"); + } + + [TestMethod] + public void WhenChangeIsOnePenny() + { + var results = cashier.GetChange(1.99m, 2); + Assert.AreEqual("1 penny", results); + } + + [TestMethod] + public void WhenChangeIsTwoPennies() + { + var results = cashier.GetChange(2.98m, 3); + Assert.AreEqual("2 pennies", results); + } + + [TestMethod] + public void WhenChangeIsOneNickel() + { + var results = cashier.GetChange(2.95m, 3); + Assert.AreEqual("1 nickel", results); + } + + [TestMethod] + public void WhenChangeIsOneDime() + { + var results = cashier.GetChange(1.90m, 2); + Assert.AreEqual("1 dime", results); + } + + [TestMethod] + public void WhenChangeIsTwoDimes() + { + var results = cashier.GetChange(2.80m, 3); + Assert.AreEqual("2 dimes", results); + } + + [TestMethod] + public void WhenChangeIsOneQuarter() + { + var results = cashier.GetChange(1.75m, 2); + Assert.AreEqual("1 quarter", results); + } + + [TestMethod] + public void WhenChangeIsTwoQuarter() + { + var results = cashier.GetChange(1.51m, 2.01m); + Assert.AreEqual("2 quarters", results); + } + + [TestMethod] + public void WhenChangeIsOneDollar() + { + var results = cashier.GetChange(1.00m, 2); + Assert.AreEqual("1 dollar", results); + } + + [TestMethod] + public void WhenChangeIsTwoDollars() + { + var results = cashier.GetChange(1.00m, 3); + Assert.AreEqual("2 dollars", results); + } + + [TestMethod] + public void WhenChangeIsOneFive() + { + var results = cashier.GetChange(10.00m, 15.0m); + Assert.AreEqual("1 five", results); + } + + [TestMethod] + public void WhenChangeIsOneTen() + { + var results = cashier.GetChange(10.00m, 20); + Assert.AreEqual("1 ten", results); + } + + [TestMethod] + public void WhenChangeIsOneTwenty() + { + var results = cashier.GetChange(80.00m, 100); + Assert.AreEqual("1 twenty", results); + } + + [TestMethod] + public void WhenChangeIsTwoTwenties() + { + var results = cashier.GetChange(70.00m, 110); + Assert.AreEqual("2 twenties", results); + } + + + [TestMethod] + public void WhenChangeIsOneFifty() + { + var results = cashier.GetChange(50.00m, 100); + Assert.AreEqual("1 fifty", results); + } + + + [TestMethod] + public void WhenChangeIsOneHunder() + { + var results = cashier.GetChange(100.00m, 200); + Assert.AreEqual("1 hundred", results); + } + + [TestMethod] + public void WhenChangeIsTwoHunder() + { + var results = cashier.GetChange(100.00m, 300); + Assert.AreEqual("2 hundreds", results); + } + + [TestMethod] + public void WhenChangeIs3Quarters1DimeAnd3Pennies() + { + var results = cashier.GetChange(2.12m, 3.00m); + Assert.AreEqual("3 quarters,1 dime,3 pennies", results); + } + + [TestMethod] + public void WhenChangeIs3Pennies() + { + var results = cashier.GetChange(1.97m, 2.00m); + Assert.AreEqual("3 pennies", results); + } + + [TestMethod] + public void WhenTotalIsDivisiableBy3() + { + var results = cashier.GetChange(3.33m, 5.00m); + Assert.AreNotEqual("Exact change. Nothing to be returned.", results); + } + + [TestMethod] + public void WhenMaxChangeAllowed() + { + var results = cashier.GetChange(0.01m, 21474836.48m); + Assert.AreNotEqual("Exact change. Nothing to be returned.", results); + } + + + [TestMethod] + public void WhenOverMaxChangeAllowed() + { + try + { + cashier.GetChange(0.01m, 21474836.49m); + } + catch (Exception ex) + { + Assert.IsInstanceOfType(ex, typeof(Exception)); + Assert.AreEqual("Change amount is too large to be calculated. Must be less than or equal to 21474836.47.", ex.Message); + return; + } + + Assert.Fail("Exception was expected"); + } + + [TestMethod] + public void WhenTotalIsNegative() + { + try + { + cashier.GetChange(-0.01m, 16.49m); + } + catch (Exception ex) + { + Assert.IsInstanceOfType(ex, typeof(Exception)); + Assert.AreEqual("Total amount should be a positive amount.", ex.Message); + return; + } + + Assert.Fail("Exception was expected"); + } + + [TestMethod] + public void WhenPaidIsNegative() + { + try + { + cashier.GetChange(0.01m, -16.49m); + } + catch (Exception ex) + { + Assert.IsInstanceOfType(ex, typeof(Exception)); + Assert.AreEqual("Paid amount should be a positive amount.", ex.Message); + return; + } + + Assert.Fail("Exception was expected"); + } + } +} diff --git a/Source/CashRegister/CashRegisterLib.Tests/Properties/AssemblyInfo.cs b/Source/CashRegister/CashRegisterLib.Tests/Properties/AssemblyInfo.cs new file mode 100644 index 00000000..90e5fb63 --- /dev/null +++ b/Source/CashRegister/CashRegisterLib.Tests/Properties/AssemblyInfo.cs @@ -0,0 +1,19 @@ +using System.Reflection; +using System.Runtime.InteropServices; + +[assembly: AssemblyTitle("CashRegisterLib.Tests")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("Creative Cash Draw Solutions")] +[assembly: AssemblyProduct("CashRegisterLib.Tests")] +[assembly: AssemblyCopyright("Copyright © 2019 Richard Todosichuk")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +[assembly: ComVisible(false)] + +[assembly: Guid("4fc57344-f83d-4185-879a-0b5295f89221")] + +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/Source/CashRegister/CashRegisterLib.Tests/packages.config b/Source/CashRegister/CashRegisterLib.Tests/packages.config new file mode 100644 index 00000000..2f7c5a18 --- /dev/null +++ b/Source/CashRegister/CashRegisterLib.Tests/packages.config @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/Source/CashRegister/CashRegisterLib/CashRegisterLib.csproj b/Source/CashRegister/CashRegisterLib/CashRegisterLib.csproj new file mode 100644 index 00000000..623a6a2a --- /dev/null +++ b/Source/CashRegister/CashRegisterLib/CashRegisterLib.csproj @@ -0,0 +1,48 @@ + + + + + Debug + AnyCPU + {84DE2951-4E59-4ADA-AFE6-DADAA8833539} + Library + Properties + CashRegisterLib + CashRegisterLib + v4.7.2 + 512 + true + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Source/CashRegister/CashRegisterLib/Cashier.cs b/Source/CashRegister/CashRegisterLib/Cashier.cs new file mode 100644 index 00000000..c22d3ffa --- /dev/null +++ b/Source/CashRegister/CashRegisterLib/Cashier.cs @@ -0,0 +1,210 @@ +using System; +using System.Text; + +namespace CashRegisterLib +{ + sealed public class Cashier + { + int hundreds = 0; + int fifties = 0; + int twenties = 0; + int tens = 0; + int fives = 0; + int dollars = 0; + int quarters = 0; + int dimes = 0; + int nickels = 0; + int pennies = 0; + + static Random random = new Random(); + + public string GetChange(decimal total, decimal paid) + { + // round to the penny + total = Math.Round(total, 2); + paid = Math.Round(paid, 2); + + // validate + if (total < 0) + throw new Exception("Total amount should be a positive amount."); + + if (paid < 0) + throw new Exception("Paid amount should be a positive amount."); + + if (paid < total) + throw new Exception("Please pay more. Paid amount is less than the total amount."); + + if (paid - total > Int32.MaxValue / 100m) + throw new Exception($"Change amount is too large to be calculated. Must be less than or equal to {Int32.MaxValue / 100m}."); + + // clear and calculate + Reset(); + + var change = paid - total; + if ((total % 0.03m) != 0) + { + // the minimum amount of physical change + CalcChange(change); + } + else + { + // if the total due in cents is divisible by 3, the app should randomly generate the change denominations + var cnt = 0; + while (change > 0 && cnt < 3) + { + var subChange = random.Next(Convert.ToInt32(change * 100)) / 100.0m; + CalcChange(subChange); + change -= subChange; + cnt++; + } + CalcChange(change); + } + return GetDisplayString(); + } + + private void Reset() + { + hundreds = 0; + fifties = 0; + twenties = 0; + tens = 0; + fives = 0; + dollars = 0; + quarters = 0; + dimes = 0; + nickels = 0; + pennies = 0; + } + + private void CalcChange(decimal change) + { + var c = Math.Floor(change / 100); + if (c > 0) + { + hundreds += (int)c; + change -= c * 100; + } + + c = Math.Floor(change / 50); + if (c > 0) + { + fifties = (int)c; + change -= c * 50; + } + + c = Math.Floor(change / 20); + if (c > 0) + { + twenties += (int)c; + change -= c * 20; + } + + c = Math.Floor(change / 10); + if (c > 0) + { + tens += (int)c; + change -= c * 10; + } + + c = Math.Floor(change / 5); + if (c > 0) + { + fives += (int)c; + change -= c * 5; + } + + c = Math.Floor(change / 1); + if (c > 0) + { + dollars += (int)c; + change -= c; + } + + c = Math.Floor(change / 0.25m); + if (c > 0) + { + quarters += (int)c; + change -= c * 0.25m; + } + + c = Math.Floor(change / 0.10m); + if (c > 0) + { + dimes += (int)c; + change -= c * 0.10m; + } + + c = Math.Floor(change / 0.05m); + if (c > 0) + { + nickels += (int)c; + change -= c * 0.05m; + } + + c = change / 0.01m; + if (c > 0) + { + pennies += (int)c; + } + } + + private string GetDisplayString() + { + var changeStr = new StringBuilder(); + if (hundreds > 0) + { + changeStr.Append(hundreds == 1 ? $"{hundreds} hundred" : $"{hundreds} hundreds"); + } + if (fifties > 0) + { + if (changeStr.Length > 0) changeStr.Append(","); + changeStr.Append(fifties == 1 ? $"{fifties} fifty" : $"{fifties} fifties"); + } + if (twenties > 0) + { + if (changeStr.Length > 0) changeStr.Append(","); + changeStr.Append(twenties == 1 ? $"{twenties} twenty" : $"{twenties} twenties"); + } + if (tens > 0) + { + if (changeStr.Length > 0) changeStr.Append(","); + changeStr.Append(tens == 1 ? $"{tens} ten" : $"{tens} tens"); + } + if (fives > 0) + { + if (changeStr.Length > 0) changeStr.Append(","); + changeStr.Append(fives == 1 ? $"{fives} five" : $"{fives} fives"); + } + if (dollars > 0) + { + if (changeStr.Length > 0) changeStr.Append(","); + changeStr.Append(dollars == 1 ? $"{dollars} dollar" : $"{dollars} dollars"); + } + if (quarters > 0) + { + if (changeStr.Length > 0) changeStr.Append(","); + changeStr.Append(quarters == 1 ? $"{quarters} quarter" : $"{quarters} quarters"); + } + if (dimes > 0) + { + if (changeStr.Length > 0) changeStr.Append(","); + changeStr.Append(dimes == 1 ? $"{dimes} dime" : $"{dimes} dimes"); + } + if (nickels > 0) + { + if (changeStr.Length > 0) changeStr.Append(","); + changeStr.Append(nickels == 1 ? $"{nickels} nickel" : $"{nickels} nickels"); + } + if (pennies > 0) + { + if (changeStr.Length > 0) changeStr.Append(","); + changeStr.Append(pennies == 1 ? $"{pennies} penny" : $"{pennies} pennies"); + } + + if (changeStr.Length > 0) + return changeStr.ToString(); + + return "Exact change. Nothing to be returned."; + } + } +} diff --git a/Source/CashRegister/CashRegisterLib/Properties/AssemblyInfo.cs b/Source/CashRegister/CashRegisterLib/Properties/AssemblyInfo.cs new file mode 100644 index 00000000..98f936a4 --- /dev/null +++ b/Source/CashRegister/CashRegisterLib/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("CashRegisterLib")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("Creative Cash Draw Solutions")] +[assembly: AssemblyProduct("CashRegisterLib")] +[assembly: AssemblyCopyright("Copyright © 2019 Richard Todosichuk")] +[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("84de2951-4e59-4ada-afe6-dadaa8833539")] + +// 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")] diff --git a/Source/CashRegister/InputFiles/Bad.csv b/Source/CashRegister/InputFiles/Bad.csv new file mode 100644 index 00000000..e2fa775d --- /dev/null +++ b/Source/CashRegister/InputFiles/Bad.csv @@ -0,0 +1,13 @@ +0.003,-0.03 +-0.003,0 +A,B +A +1 +, +20, +,20 +12,20m +12h,20m +12.1.2,20 +12.12,"20" +12.12|20 \ No newline at end of file diff --git a/Source/CashRegister/InputFiles/BiggerValues.csv b/Source/CashRegister/InputFiles/BiggerValues.csv new file mode 100644 index 00000000..dfbab44c --- /dev/null +++ b/Source/CashRegister/InputFiles/BiggerValues.csv @@ -0,0 +1,5 @@ +444.44,1000.14 +33333.33,40000.00 +103333.33,200000.00 +0.01,21474836.48 +0,21474836.47 \ No newline at end of file diff --git a/Source/CashRegister/InputFiles/BlankLines.csv b/Source/CashRegister/InputFiles/BlankLines.csv new file mode 100644 index 00000000..d5f27b1b --- /dev/null +++ b/Source/CashRegister/InputFiles/BlankLines.csv @@ -0,0 +1,7 @@ + + + 2.12,3.00 + +1.97 ,2.00 + +3.33, 5.00 \ No newline at end of file diff --git a/Source/CashRegister/InputFiles/Empty.csv b/Source/CashRegister/InputFiles/Empty.csv new file mode 100644 index 00000000..e69de29b diff --git a/Source/CashRegister/InputFiles/Sample1.csv b/Source/CashRegister/InputFiles/Sample1.csv new file mode 100644 index 00000000..fba6116f --- /dev/null +++ b/Source/CashRegister/InputFiles/Sample1.csv @@ -0,0 +1,3 @@ +2.12,3.00 +1.97,2.00 +3.33,5.00 \ No newline at end of file diff --git a/Source/CashRegister/InputFiles/ZeroValues.csv b/Source/CashRegister/InputFiles/ZeroValues.csv new file mode 100644 index 00000000..febdbda7 --- /dev/null +++ b/Source/CashRegister/InputFiles/ZeroValues.csv @@ -0,0 +1,5 @@ +0,0.1 +0,0 +0.000000000001,0.00000001 +0,555.55 +0.00001,0.09999 \ No newline at end of file