diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7899820 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +.vs/ +bin/ +obj/ +Debug/ +Release/ diff --git a/README.md b/README.md index b68f928..0350974 100644 --- a/README.md +++ b/README.md @@ -1,27 +1,17 @@ -# simpleOrderSearch -simpleOrderSearch +This is my SimpleOrderSearch REST API service (and console client) attempt after ~4 hours of work. This was my first time creating a REST API service from scratch and my first ever ASP.net project, so the ~4 hours are both learning and doing. +The concepts seemed somewhat familiar/easy, like how routes work. Fair amount of fumbling about for exact class/nuget-package to do what I want. -I want to assess your ability to create an application and REST API service. It truly is the bare minimum of knowledge necessary to be successful in this position. I don't want you to spend a lot of time on this. You should be able to do this in an hour or so if the job is right for you. +Also, the specification requiring an OrderID AND a CompletionDte seemed so +wrong that I implemented things as if valid search criteria are: + 1: OrderID, or... + 2: MSA and Status and CompletionDte -Order Search +I would change things the moment I become convinced that always requiring the +CompletionDte is a good/necessary thing. -This programming task consists of building a simple console application to search for orders. Fork this repository and create your application. It should take this input from the user: +To run my stuff, open each solution in Visual Studio 2019 and start debugging. -(Order Number || (MSA && Status)) && CompletionDte - -The console application will call a service that you create using C#. I have provided some sample data for the application in the JSON file in the data folder. - - - -The file contains an array whose elements represent orders. The data should be defined as a model in your service. - -The application calling the service can be a console app. You have total freedom to do what you want but make sure it can do these three things: - -• Validate that the user has provided the right criteria to make a search - -• Provide an offset and page value for pagination. - -• Write the outputs of the service call to a console window. - -Create a pull request once you have it working. I will clone your repository, verify that it works, and evaluate it. Please ensure you include any instructions for running that may be required. +See `example_client_session.txt` for an example of a client session with both +searching by OrderID and searching by {MSA, Status, CompletionDte} with +pagination. diff --git a/SimpleOrderSearchClient/Program.cs b/SimpleOrderSearchClient/Program.cs new file mode 100644 index 0000000..8a5ee9f --- /dev/null +++ b/SimpleOrderSearchClient/Program.cs @@ -0,0 +1,163 @@ +using RestSharp; +using System; +using System.Collections.Generic; +using System.Text.Json.Serialization; +using Newtonsoft.Json; + +namespace SimpleOrderSearchClient +{ + class Program + { + static string ApiUrl = "http://localhost:6627/api/Order"; + static RestClient Client = new RestClient(ApiUrl); + + static void Main(string[] args) + { + MenuMain(); + } + + static void MenuMain() + { + string mainChoice; + + do + { + Console.WriteLine("[Main Menu]"); + Console.WriteLine("'o' to search by OrderID"); + Console.WriteLine("'m' to search by {MSA, Status, CompletionDte}"); + Console.WriteLine("'q' to quit"); + Console.Write("your choice: "); + + mainChoice = Console.ReadLine(); + Console.WriteLine(); + + if (mainChoice == "o") + { + SearchByOrderId(); + } + else if (mainChoice == "m") + { + SearchByMsaStatusCompletionDte(); + } + + Console.WriteLine(); + } while (mainChoice != "q"); + } + + static void SearchByOrderId() + { + Console.WriteLine("[Searching By OrderID]"); + + long orderID; + if (!getUserLong("OrderID", out orderID)) + { + return; + } + + var request = new RestRequest(Method.GET); + request.AddParameter("orderID", orderID); + + var response = Client.Execute(request); + if (response.IsSuccessful) + { + Console.WriteLine(" success!"); + var orders = JsonConvert.DeserializeObject>(response.Content); + Console.WriteLine(JsonConvert.SerializeObject(orders, Formatting.Indented)); + } + else + { + Console.WriteLine(" error!"); + Console.WriteLine($" Status Code: {response.StatusCode}"); + Console.WriteLine($" Status Description: {response.StatusDescription}"); + Console.WriteLine($" Error Msg: {response.ErrorMessage}"); + } + } + + static void SearchByMsaStatusCompletionDte() + { + Console.WriteLine("[Searching By {MSA, Status, CompleteDte}]"); + long msa; + long status; + DateTime completionDte; + + if (!getUserLong("MSA", out msa) + || !getUserLong("Status", out status) + || !getUserDateTime("CompletionDte", out completionDte)) + { + return; + } + + int page = 0; + + while (displaySearchResults(msa, status, completionDte, page)) + { + page++; + Console.Write("want next page (y/n)? "); + + if (Console.ReadLine() != "y") + { + break; + } + } + } + + static bool displaySearchResults(long msa, long status, DateTime completionDte, int page) + { + var request = new RestRequest(Method.GET); + request.AddParameter("msa", msa); + request.AddParameter("status", status); + request.AddParameter("completionDte", completionDte); + request.AddParameter("page", page); + + var response = Client.Execute(request); + bool gotOrders = false; + + if (response.IsSuccessful) + { + Console.WriteLine(" success!"); + var orders = JsonConvert.DeserializeObject>(response.Content); + Console.WriteLine(JsonConvert.SerializeObject(orders, Formatting.Indented)); + + gotOrders = orders.Count > 0; + } + else + { + Console.WriteLine(" error!"); + Console.WriteLine($" Status Code: {response.StatusCode}"); + Console.WriteLine($" Status Description: {response.StatusDescription}"); + Console.WriteLine($" Error Msg: {response.ErrorMessage}"); + } + + return gotOrders; + } + + static bool getUserLong(string valName, out long val) + { + Console.Write($"enter {valName}: "); + string valStr = Console.ReadLine(); + + if (!long.TryParse(valStr, out val)) + { + Console.WriteLine($"invalid {valName}, returning to previous menu"); + return false; + } + + return true; + } + + static bool getUserDateTime(string valName, out DateTime val) + { + Console.Write($"enter {valName}: "); + string valStr = Console.ReadLine(); + + if (!DateTime.TryParse(valStr, out val)) + { + Console.WriteLine($"invalid {valName}, returning to previous menu"); + return false; + } + + return true; + } + + } +} diff --git a/SimpleOrderSearchClient/SimpleOrderSearchClient.csproj b/SimpleOrderSearchClient/SimpleOrderSearchClient.csproj new file mode 100644 index 0000000..9ee1d52 --- /dev/null +++ b/SimpleOrderSearchClient/SimpleOrderSearchClient.csproj @@ -0,0 +1,13 @@ + + + + Exe + netcoreapp3.1 + + + + + + + + diff --git a/SimpleOrderSearchClient/SimpleOrderSearchClient.sln b/SimpleOrderSearchClient/SimpleOrderSearchClient.sln new file mode 100644 index 0000000..15558a4 --- /dev/null +++ b/SimpleOrderSearchClient/SimpleOrderSearchClient.sln @@ -0,0 +1,25 @@ + +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}") = "SimpleOrderSearchClient", "SimpleOrderSearchClient.csproj", "{53C14057-1996-45B9-A465-0F0F3D573F2D}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {53C14057-1996-45B9-A465-0F0F3D573F2D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {53C14057-1996-45B9-A465-0F0F3D573F2D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {53C14057-1996-45B9-A465-0F0F3D573F2D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {53C14057-1996-45B9-A465-0F0F3D573F2D}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {57F322C1-9AD3-46B8-A62B-32D34FCA7C73} + EndGlobalSection +EndGlobal diff --git a/SimpleOrderSearchService/Controllers/OrderController.cs b/SimpleOrderSearchService/Controllers/OrderController.cs new file mode 100644 index 0000000..cefe6c8 --- /dev/null +++ b/SimpleOrderSearchService/Controllers/OrderController.cs @@ -0,0 +1,98 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using System.Web.Http.Results; +using Microsoft.AspNetCore.Mvc; +using SimpleOrderSearchService.Models; +using SimpleOrderSearchService.Services; +using SimpleOrderSearchService.Util; + +// For more information on enabling Web API for empty projects, visit https://go.microsoft.com/fwlink/?LinkID=397860 + +namespace SimpleOrderSearchService.Controllers +{ + [Route("api/[controller]")] + public class OrderController : Controller + { + private readonly IOrderService _orderService; + private readonly int _defaultItemsPerPage = 3; + + public OrderController(IOrderService orderService) + { + _orderService = orderService; + } + + // note: returning IActionResult instead of the actual data type so we can also return errors + + // GET: api/ + [HttpGet] + public IActionResult Get + ( [FromQuery] long? orderID + , [FromQuery] long? msa + , [FromQuery] long? status + , [FromQuery] DateTime? completionDte + , [FromQuery] int? page + , [FromQuery] int? itemsPerPage + ) + { + // sorry, but the specification requiring an OrderID AND a + // CompletionDte seems so wrong that I'll be implementing things as + // if valid search criteria are: + // 1: OrderID + // 2: MSA and Status and CompletionDte + // and am very open to changing things once I become convinced that + // always requiring the CompletionDte is a good/necessary thing + + // yes, I am assuming OrderID will be unique + + if (orderID.HasValue) + { + return Ok(_orderService.GetByOrderID(orderID.Value)); + } + else if (msa.HasValue && status.HasValue && completionDte.HasValue) + { + var safePageIdx = page.HasValue ? page.Value : 0; + var safeItemsPerPage = itemsPerPage.HasValue ? itemsPerPage.Value : _defaultItemsPerPage; + + var orders = _orderService.GetByMsaStatusCompletionDte(msa.Value, status.Value, completionDte.Value); + var pagedOrders = Pagination.SelectItems(orders, safeItemsPerPage, safePageIdx); + return Ok(pagedOrders); + } + + return ValidationProblem("you need to specify orderID or {msa, status, completionDte}"); + //return Ok(_orderService.GetAll()); + } + + // GET api//5 + [HttpGet("{orderID}")] + public IActionResult Get(long orderID) + { + var order = _orderService.GetByOrderID(orderID).FirstOrDefault(); + if (order == null) + { + return NotFound(); + } + + return Ok(order); + } + + // POST api/ + [HttpPost] + public void Post([FromBody]string value) + { + } + + // PUT api//5 + [HttpPut("{id}")] + public void Put(int id, [FromBody]string value) + { + } + + // DELETE api//5 + [HttpDelete("{id}")] + public void Delete(int id) + { + } + } +} diff --git a/data/orderInfo.json b/SimpleOrderSearchService/Data/orderInfo.json similarity index 100% rename from data/orderInfo.json rename to SimpleOrderSearchService/Data/orderInfo.json diff --git a/SimpleOrderSearchService/Models/Order.cs b/SimpleOrderSearchService/Models/Order.cs new file mode 100644 index 0000000..1655921 --- /dev/null +++ b/SimpleOrderSearchService/Models/Order.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace SimpleOrderSearchService.Models +{ + public class Order + { + public long OrderID { get; set; } + public long ShipperID { get; set; } + public long DriverID { get; set; } + public DateTime CompletionDte { get; set; } + public long Status { get; set; } + public string Code { get; set; } + public long MSA { get; set; } + public decimal Duration { get; set; } + public long OfferType { get; set; } + } +} diff --git a/SimpleOrderSearchService/Program.cs b/SimpleOrderSearchService/Program.cs new file mode 100644 index 0000000..533325e --- /dev/null +++ b/SimpleOrderSearchService/Program.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; + +namespace SimpleOrderSearchService +{ + public class Program + { + public static void Main(string[] args) + { + CreateHostBuilder(args).Build().Run(); + } + + public static IHostBuilder CreateHostBuilder(string[] args) => + Host.CreateDefaultBuilder(args) + .ConfigureWebHostDefaults(webBuilder => + { + webBuilder.UseStartup(); + }); + } +} diff --git a/SimpleOrderSearchService/Properties/launchSettings.json b/SimpleOrderSearchService/Properties/launchSettings.json new file mode 100644 index 0000000..c9d4c76 --- /dev/null +++ b/SimpleOrderSearchService/Properties/launchSettings.json @@ -0,0 +1,30 @@ +{ + "$schema": "http://json.schemastore.org/launchsettings.json", + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:6627", + "sslPort": 44357 + } + }, + "profiles": { + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "launchUrl": "api/Order", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "SimpleOrderSearchService": { + "commandName": "Project", + "launchBrowser": true, + "launchUrl": "api/Order", + "applicationUrl": "https://localhost:5001;http://localhost:5000", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/SimpleOrderSearchService/Services/IOrderService.cs b/SimpleOrderSearchService/Services/IOrderService.cs new file mode 100644 index 0000000..024a624 --- /dev/null +++ b/SimpleOrderSearchService/Services/IOrderService.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using SimpleOrderSearchService.Models; + +namespace SimpleOrderSearchService.Services +{ + public interface IOrderService + { + IEnumerable GetByOrderID(long orderID); + IEnumerable GetByMsaStatusCompletionDte(long msa, long status, DateTime completionDte); + } +} diff --git a/SimpleOrderSearchService/Services/OrderService.cs b/SimpleOrderSearchService/Services/OrderService.cs new file mode 100644 index 0000000..d06d591 --- /dev/null +++ b/SimpleOrderSearchService/Services/OrderService.cs @@ -0,0 +1,39 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.Extensions.Hosting.Internal; +using Newtonsoft.Json; +using SimpleOrderSearchService.Models; + +namespace SimpleOrderSearchService.Services +{ + public class OrderService : IOrderService + { + public IEnumerable GetAll() + { + string jsonBlob; + using (var reader = new StreamReader("Data/orderInfo.json")) + { + jsonBlob = reader.ReadToEnd(); + } + + var orders = JsonConvert.DeserializeObject>(jsonBlob); + return orders; + } + + public IEnumerable GetByOrderID(long orderID) + { + return GetAll().Where(order => order.OrderID == orderID); + } + + public IEnumerable GetByMsaStatusCompletionDte(long msa, long status, DateTime completionDte) + { + return GetAll().Where( order + => order.MSA == msa + && order.Status == status + && order.CompletionDte == completionDte); + } + } +} diff --git a/SimpleOrderSearchService/SimpleOrderSearchService.csproj b/SimpleOrderSearchService/SimpleOrderSearchService.csproj new file mode 100644 index 0000000..3dab964 --- /dev/null +++ b/SimpleOrderSearchService/SimpleOrderSearchService.csproj @@ -0,0 +1,21 @@ + + + + netcoreapp3.1 + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + diff --git a/SimpleOrderSearchService/SimpleOrderSearchService.sln b/SimpleOrderSearchService/SimpleOrderSearchService.sln new file mode 100644 index 0000000..692692b --- /dev/null +++ b/SimpleOrderSearchService/SimpleOrderSearchService.sln @@ -0,0 +1,25 @@ + +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}") = "SimpleOrderSearchService", "SimpleOrderSearchService.csproj", "{C9425568-7756-42D3-ABF1-0EF47AFF3C9F}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {C9425568-7756-42D3-ABF1-0EF47AFF3C9F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C9425568-7756-42D3-ABF1-0EF47AFF3C9F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C9425568-7756-42D3-ABF1-0EF47AFF3C9F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C9425568-7756-42D3-ABF1-0EF47AFF3C9F}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {01680F85-A87A-4FD2-B460-DFE53DAC79A5} + EndGlobalSection +EndGlobal diff --git a/SimpleOrderSearchService/SimpleOrderSearchService.sln.DotSettings.user b/SimpleOrderSearchService/SimpleOrderSearchService.sln.DotSettings.user new file mode 100644 index 0000000..d1ab630 --- /dev/null +++ b/SimpleOrderSearchService/SimpleOrderSearchService.sln.DotSettings.user @@ -0,0 +1,4 @@ + + <AssemblyExplorer> + <Assembly Path="C:\Program Files\dotnet\packs\Microsoft.AspNetCore.App.Ref\3.1.0\ref\netcoreapp3.1\Microsoft.AspNetCore.Mvc.Core.dll" /> +</AssemblyExplorer> \ No newline at end of file diff --git a/SimpleOrderSearchService/Startup.cs b/SimpleOrderSearchService/Startup.cs new file mode 100644 index 0000000..f9f9b03 --- /dev/null +++ b/SimpleOrderSearchService/Startup.cs @@ -0,0 +1,51 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.HttpsPolicy; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; +using SimpleOrderSearchService.Services; + +namespace SimpleOrderSearchService +{ + public class Startup + { + public Startup(IConfiguration configuration) + { + Configuration = configuration; + } + + public IConfiguration Configuration { get; } + + // This method gets called by the runtime. Use this method to add services to the container. + public void ConfigureServices(IServiceCollection services) + { + services.AddTransient(); + services.AddControllers(); + } + + // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. + public void Configure(IApplicationBuilder app, IWebHostEnvironment env) + { + if(env.IsDevelopment()) + { + app.UseDeveloperExceptionPage(); + } + + //app.UseHttpsRedirection(); + + app.UseRouting(); + app.UseAuthorization(); + app.UseEndpoints(endpoints => + { + endpoints.MapControllers(); + }); + } + } +} diff --git a/SimpleOrderSearchService/Util/Pagination.cs b/SimpleOrderSearchService/Util/Pagination.cs new file mode 100644 index 0000000..511cd14 --- /dev/null +++ b/SimpleOrderSearchService/Util/Pagination.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace SimpleOrderSearchService.Util +{ + public static class Pagination + { + public static IEnumerable SelectItems(IEnumerable items, int itemsPerPage, int pageIdx) + { + return items.Skip(pageIdx * itemsPerPage).Take(itemsPerPage); + } + } +} diff --git a/SimpleOrderSearchService/WeatherForecast.cs b/SimpleOrderSearchService/WeatherForecast.cs new file mode 100644 index 0000000..9cf98d8 --- /dev/null +++ b/SimpleOrderSearchService/WeatherForecast.cs @@ -0,0 +1,15 @@ +using System; + +namespace SimpleOrderSearchService +{ + public class WeatherForecast + { + public DateTime Date { get; set; } + + public int TemperatureC { get; set; } + + public int TemperatureF => 32 + (int)(TemperatureC / 0.5556); + + public string Summary { get; set; } + } +} diff --git a/SimpleOrderSearchService/appsettings.Development.json b/SimpleOrderSearchService/appsettings.Development.json new file mode 100644 index 0000000..8983e0f --- /dev/null +++ b/SimpleOrderSearchService/appsettings.Development.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft": "Warning", + "Microsoft.Hosting.Lifetime": "Information" + } + } +} diff --git a/SimpleOrderSearchService/appsettings.json b/SimpleOrderSearchService/appsettings.json new file mode 100644 index 0000000..d9d9a9b --- /dev/null +++ b/SimpleOrderSearchService/appsettings.json @@ -0,0 +1,10 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft": "Warning", + "Microsoft.Hosting.Lifetime": "Information" + } + }, + "AllowedHosts": "*" +} diff --git a/example_client_session.txt b/example_client_session.txt new file mode 100644 index 0000000..f3d1e9d --- /dev/null +++ b/example_client_session.txt @@ -0,0 +1,114 @@ +[Main Menu] +'o' to search by OrderID +'m' to search by {MSA, Status, CompletionDte} +'q' to quit +your choice: o + +[Searching By OrderID] +enter OrderID: asdf +invalid OrderID, returning to previous menu + +[Main Menu] +'o' to search by OrderID +'m' to search by {MSA, Status, CompletionDte} +'q' to quit +your choice: o + +[Searching By OrderID] +enter OrderID: 40 +success! +[ + { + "orderID": 40, + "shipperID": 4, + "driverID": 35, + "completionDte": "2018-01-31T05:10:00", + "status": 10, + "code": "R4C9999F", + "msa": 1, + "duration": 111.0, + "offerType": 1 + } +] + +[Main Menu] +'o' to search by OrderID +'m' to search by {MSA, Status, CompletionDte} +'q' to quit +your choice: m + +[Searching By {MSA, Status, CompleteDte}] +enter MSA: 1 +enter Status: 10 +enter CompletionDte: 2018-01-31T05:10:00 +success! +[ + { + "orderID": 40, + "shipperID": 4, + "driverID": 35, + "completionDte": "2018-01-31T05:10:00", + "status": 10, + "code": "R4C9999F", + "msa": 1, + "duration": 111.0, + "offerType": 1 + }, + { + "orderID": 41, + "shipperID": 67, + "driverID": 35, + "completionDte": "2018-01-31T05:10:00", + "status": 10, + "code": "R4C87S32", + "msa": 1, + "duration": 54.0, + "offerType": 1 + }, + { + "orderID": 42, + "shipperID": 4, + "driverID": 35, + "completionDte": "2018-01-31T05:10:00", + "status": 10, + "code": "R4C87123", + "msa": 1, + "duration": 92.0, + "offerType": 1 + } +] +want next page (y/n)? y +success! +[ + { + "orderID": 43, + "shipperID": 4, + "driverID": 35, + "completionDte": "2018-01-31T05:10:00", + "status": 10, + "code": "R42G77FF", + "msa": 1, + "duration": 40.0, + "offerType": 1 + }, + { + "orderID": 44, + "shipperID": 4, + "driverID": 35, + "completionDte": "2018-01-31T05:10:00", + "status": 10, + "code": "R4002WFF", + "msa": 1, + "duration": 92.0, + "offerType": 2 + } +] +want next page (y/n)? y +success! +[] + +[Main Menu] +'o' to search by OrderID +'m' to search by {MSA, Status, CompletionDte} +'q' to quit +your choice: q