Compare commits

...

1 Commits

Author SHA1 Message Date
Jakub Łangowski
281f9d6944 Init 2021-10-12 19:09:29 +02:00
28 changed files with 949 additions and 0 deletions

8
s452708/.gitignore vendored Normal file
View File

@ -0,0 +1,8 @@
bin/
obj/
/packages/
riderModule.iml
/_ReSharper.Caches/
.idea/
.vs/
**.user

16
s452708/Automat.sln Normal file
View File

@ -0,0 +1,16 @@

Microsoft Visual Studio Solution File, Format Version 12.00
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Automat", "Automat\Automat.csproj", "{F5FD2DB0-1E52-491F-BBE4-10EF781FA998}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{F5FD2DB0-1E52-491F-BBE4-10EF781FA998}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{F5FD2DB0-1E52-491F-BBE4-10EF781FA998}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F5FD2DB0-1E52-491F-BBE4-10EF781FA998}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F5FD2DB0-1E52-491F-BBE4-10EF781FA998}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
EndGlobal

239
s452708/Automat/.gitignore vendored Normal file
View File

@ -0,0 +1,239 @@
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
Storage/Recipes/*
Storage/Categories/*
!Storage/Recipes/nie_usuwac.txt
!Storage/Categories/nie_usuwac.txt
Migrations/
Migrations/*
Migrations
# User-specific files
*.suo
*.user
*.userosscache
*.sln.docstates
# User-specific files (MonoDevelop/Xamarin Studio)
*.userprefs
# Build results
[Dd]ebug/
[Dd]ebugPublic/
[Rr]elease/
[Rr]eleases/
x64/
x86/
build/
bld/
bin/
Bin/
obj/
Obj/
# Visual Studio 2015 cache/options directory
.vs/
/wwwroot/dist/
# 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
*_i.c
*_p.c
*_i.h
*.ilk
*.meta
*.obj
*.pch
*.pdb
*.pgc
*.pgd
*.rsp
*.sbr
*.tlb
*.tli
*.tlh
*.tmp
*.tmp_proj
*.log
*.vspscc
*.vssscc
.builds
*.pidb
*.svclog
*.scc
# Chutzpah Test files
_Chutzpah*
# Visual C++ cache files
ipch/
*.aps
*.ncb
*.opendb
*.opensdf
*.sdf
*.cachefile
# Visual Studio profiler
*.psess
*.vsp
*.vspx
*.sap
# 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
# 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
*.pubxml
*.publishproj
# NuGet Packages
*.nupkg
# The packages folder can be ignored because of Package Restore
**/packages/*
# except build/, which is used as an MSBuild target.
!**/packages/build/
# Uncomment if necessary however generally it will be regenerated when needed
#!**/packages/repositories.config
# Microsoft Azure Build Output
csx/
*.build.csdef
# Microsoft Azure Emulator
ecf/
rcf/
# Microsoft Azure ApplicationInsights config file
ApplicationInsights.config
# Windows Store app package directory
AppPackages/
BundleArtifacts/
# 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
*.pfx
*.publishsettings
orleans.codegen.cs
/node_modules
# 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
# SQL Server files
*.mdf
*.ldf
# Business Intelligence projects
*.rdl.data
*.bim.layout
*.bim_*.settings
# Microsoft Fakes
FakesAssemblies/
# GhostDoc plugin setting file
*.GhostDoc.xml
# Node.js Tools for Visual Studio
.ntvs_analysis.dat
# Visual Studio 6 build log
*.plg
# Visual Studio 6 workspace options file
*.opt
# 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
# FAKE - F# Make
.fake/

View File

@ -0,0 +1,20 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net5.0</TargetFramework>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="ConsoleTables" Version="2.4.2" />
<PackageReference Include="Microsoft.Extensions.Hosting" Version="5.0.0" />
</ItemGroup>
<ItemGroup>
<None Update="appsettings.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
</ItemGroup>
</Project>

View File

@ -0,0 +1,64 @@
using System;
using System.Threading;
using System.Threading.Tasks;
using Automat.Exceptions;
using Automat.Models;
using Automat.Services.Interfaces;
using Microsoft.Extensions.Hosting;
namespace Automat
{
public class EntryService : IHostedService
{
private readonly IShoppingService _shoppingService;
private readonly ICheckoutService _checkoutService;
public EntryService(
IShoppingService shoppingService,
ICheckoutService checkoutService
)
{
_shoppingService = shoppingService;
_checkoutService = checkoutService;
}
public async Task StartAsync(CancellationToken cancellationToken)
{
await Task.Run(async () =>
{
while (true)
{
try
{
await Handle();
}
catch (RestartException e) { }
}
}, cancellationToken);
}
private async Task Handle()
{
await _shoppingService.ListProducts();
Product product = await _shoppingService.PickProduct();
await _checkoutService.ListPaymentOptions();
PaymentOption paymentOption = await _checkoutService.PickPaymentOption();
bool paymentResult = await _checkoutService.Checkout(paymentOption.Type, product.Price);
if (!paymentResult) return;
await _shoppingService.ServeMeal(product);
Console.WriteLine("Press any key to continue");
Console.ReadLine();
Console.Clear();
}
public Task StopAsync(CancellationToken cancellationToken)
{
return Task.CompletedTask;
}
}
}

View File

@ -0,0 +1,9 @@
using System;
namespace Automat.Exceptions
{
public class RestartException : Exception
{
}
}

View File

@ -0,0 +1,22 @@
namespace Automat.Models
{
public enum PaymentOptionType
{
Cash,
Card
}
public class PaymentOption
{
public PaymentOption(int id, PaymentOptionType type, string name)
{
Id = id;
Type = type;
Name = name;
}
public int Id { get; set; }
public PaymentOptionType Type { get; set; }
public string Name { get; set; }
}
}

View File

@ -0,0 +1,39 @@
using System;
namespace Automat.Models
{
public enum ProductType
{
Drink,
Candy,
MainDish
}
public class Product
{
public Product(Guid id, int code, ProductType type, string name, int price, int stock)
{
Id = id;
Code = code;
Type = type;
Name = name;
Price = price;
Stock = stock;
}
public Guid Id { get; private set; }
public int Code { get; private set; }
public ProductType Type { get; private set; }
public string Name { get; private set; }
public int Price { get; private set; }
public int Stock { get; private set; }
public string PriceFormatted => (Price / 100m).ToString("C2");
public int DecreaseStock()
{
Stock -= 1;
return Stock;
}
}
}

View File

@ -0,0 +1,14 @@
namespace Automat.Models
{
public class Result<T> where T : class
{
public Result(bool restart, T value)
{
Restart = restart;
Value = value;
}
public bool Restart { get; } = false;
public T Value { get; }
}
}

View File

@ -0,0 +1,7 @@
namespace Automat.Options
{
public class AppSettings
{
public int LowStockThreshold { get; set; }
}
}

View File

@ -0,0 +1,57 @@
using System;
using System.IO;
using System.Threading.Tasks;
using Automat.Options;
using Automat.Repositories;
using Automat.Repositories.Interfaces;
using Automat.Services;
using Automat.Services.Factories;
using Automat.Services.Interfaces;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
namespace Automat
{
class Program
{
static async Task Main(string[] args)
{
var environment = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT");
var builder = new ConfigurationBuilder();
builder
.AddJsonFile(Path.Combine(AppDomain.CurrentDomain.BaseDirectory,"appsettings.json"), optional: false)
.AddJsonFile(Path.Combine(AppDomain.CurrentDomain.BaseDirectory,$"appsettings.{environment}.json"), optional: true);
Configuration = builder.Build();
await CreateHostBuilder(args).Build().RunAsync();
}
private static IConfiguration Configuration { get; set; }
private static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureAppConfiguration(builder =>
{
builder.Sources.Clear();
builder.AddConfiguration(Configuration);
})
.ConfigureServices(services =>
{
services.Configure<AppSettings>(Configuration.GetSection("DataProtector"));
services.AddSingleton<IHostedService, EntryService>();
services.AddSingleton<IDisplayService, DisplayService>();
services.AddSingleton<IPaymentGatewayFactory, PaymentGatewayFactory>();
services.AddSingleton<IProductRepository, ProductRepository>();
services.AddSingleton<IPaymentOptionRepository, PaymentOptionRepository>();
services.AddScoped<IShoppingService, ShoppingService>();
services.AddScoped<ICheckoutService, CheckoutService>();
})
.UseDefaultServiceProvider(options => options.ValidateScopes = false)
.UseConsoleLifetime();
}
}

View File

@ -0,0 +1,12 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using Automat.Models;
namespace Automat.Repositories.Interfaces
{
public interface IPaymentOptionRepository
{
Task<List<PaymentOption>> GetAll();
Task<PaymentOption?> Get(int id);
}
}

View File

@ -0,0 +1,14 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Automat.Models;
namespace Automat.Repositories.Interfaces
{
public interface IProductRepository
{
Task<List<Product>> GetAllProducts();
Task<Product?> GetProduct(int productId);
Task<int> DecreaseStock(int productId);
}
}

View File

@ -0,0 +1,27 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Automat.Models;
using Automat.Repositories.Interfaces;
namespace Automat.Repositories
{
public class PaymentOptionRepository : IPaymentOptionRepository
{
private List<PaymentOption> _mockDb = new List<PaymentOption>()
{
new(1, PaymentOptionType.Cash, "Płatność gotówką"),
new(2, PaymentOptionType.Card, "Płatność kartą")
};
public Task<List<PaymentOption>> GetAll()
{
return Task.FromResult(_mockDb);
}
public Task<PaymentOption?> Get(int id)
{
return Task.FromResult(_mockDb.FirstOrDefault(x => x.Id == id));
}
}
}

View File

@ -0,0 +1,38 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Automat.Models;
using Automat.Repositories.Interfaces;
namespace Automat.Repositories
{
public class ProductRepository : IProductRepository
{
private List<Product> _mockDatabase = new List<Product>
{
new (Guid.NewGuid(), 1, ProductType.Drink, "Cola", 650, 2),
new (Guid.NewGuid(), 2, ProductType.Drink, "Woda", 200, 1),
new (Guid.NewGuid(), 3, ProductType.Candy, "Czekolada", 300, 3),
new (Guid.NewGuid(), 4, ProductType.MainDish, "Zupa pomidorowa", 900, 5)
};
public Task<List<Product>> GetAllProducts()
{
return Task.FromResult(_mockDatabase);
}
public Task<Product?> GetProduct(int productId)
{
return Task.FromResult(_mockDatabase.FirstOrDefault(x => x.Code == productId));
}
public Task<int> DecreaseStock(int productId)
{
var product = _mockDatabase.FirstOrDefault(x => x.Code == productId);
if (product is null) return Task.FromResult(-1);
return Task.FromResult(product.DecreaseStock());
}
}
}

View File

@ -0,0 +1,23 @@
using System.Threading.Tasks;
using Automat.Services.Interfaces;
namespace Automat.Services
{
public class CardPaymentGateway : IPaymentGateway
{
private readonly IDisplayService _displayService;
public CardPaymentGateway(IDisplayService displayService)
{
_displayService = displayService;
}
public Task<bool> HandlePayment(int totalPrice)
{
_displayService.WriteToDisplay("Connecting to bank");
_displayService.WriteToDisplay("Waitning for result");
_displayService.WriteToDisplay("Cash Payment OK");
return Task.FromResult(true);
}
}
}

View File

@ -0,0 +1,38 @@
using System.Threading.Tasks;
using Automat.Services.Interfaces;
namespace Automat.Services
{
public class CashPaymentGateway : IPaymentGateway
{
private readonly IDisplayService _displayService;
private int _collected = 0;
public CashPaymentGateway(IDisplayService displayService)
{
_displayService = displayService;
}
public Task<bool> HandlePayment(int totalPrice)
{
_displayService.WriteToDisplay("Cash Payment OK");
return Task.FromResult(true);
}
private bool CollectMoney(int totalPrice)
{
while (_collected < totalPrice)
{
// Todo Handle coin payment
break;
}
return true;
}
private void ReturnChange()
{
// Todo Handle change return
}
}
}

View File

@ -0,0 +1,53 @@
using System;
using System.Threading.Tasks;
using Automat.Models;
using Automat.Repositories.Interfaces;
using Automat.Services.Factories;
using Automat.Services.Interfaces;
namespace Automat.Services
{
public class CheckoutService : ICheckoutService
{
private readonly IPaymentOptionRepository _paymentOptionRepository;
private readonly IPaymentGatewayFactory _paymentGatewayFactory;
private readonly IDisplayService _displayService;
public CheckoutService(
IPaymentOptionRepository paymentOptionRepository,
IPaymentGatewayFactory paymentGatewayFactory,
IDisplayService displayService
)
{
_paymentOptionRepository = paymentOptionRepository;
_paymentGatewayFactory = paymentGatewayFactory;
_displayService = displayService;
}
public async Task ListPaymentOptions()
{
_displayService.PrintSeparatorWithTitle("Wybierz opcję płatności");
_displayService.WriteListToDisplay(await _paymentOptionRepository.GetAll());
}
public async Task<PaymentOption> PickPaymentOption()
{
PaymentOption? paymentOption = null;
while (paymentOption is null)
{
var id = _displayService.ReadNumberFromDisplay();
paymentOption = await _paymentOptionRepository.Get(id);
}
return paymentOption;
}
public async Task<bool> Checkout(PaymentOptionType type, int totalPrice)
{
_displayService.PrintSeparatorWithTitle("Starting Checkout");
var paymentGateway = _paymentGatewayFactory.Create(type);
return await paymentGateway.HandlePayment(totalPrice);
}
}
}

View File

@ -0,0 +1,88 @@
using System;
using System.Collections.Generic;
using System.Text;
using Automat.Exceptions;
using Automat.Services.Interfaces;
using ConsoleTables;
using Microsoft.Extensions.Hosting;
namespace Automat.Services
{
public class DisplayService : IDisplayService
{
private readonly IHostApplicationLifetime _hostApplicationLifetime;
private const int SeparatorLen = 64;
public DisplayService(IHostApplicationLifetime hostApplicationLifetime)
{
_hostApplicationLifetime = hostApplicationLifetime;
}
public void PrintSeparator()
{
var builder = new StringBuilder();
builder.Append('-', SeparatorLen);
Console.WriteLine(builder.ToString());
}
public void PrintSeparatorWithTitle(string title)
{
var builder = new StringBuilder();
builder.Append('-', SeparatorLen);
builder.Append("\n");
var dashLen = (SeparatorLen - title.Length - 2) / 2;
builder.Append('-', dashLen);
builder.Append(' ');
builder.Append(title);
builder.Append(' ');
builder.Append('-', dashLen);
int temp = dashLen * 2 + title.Length + 2;
if (temp < SeparatorLen)
{
builder.Append('-', SeparatorLen - temp);
}
builder.Append("\n");
builder.Append('-', SeparatorLen);
Console.WriteLine(builder.ToString());
}
public void WriteToDisplay(string message)
{
Console.WriteLine(message);
}
public void WriteListToDisplay<T>(IEnumerable<T> list) where T : class
{
ConsoleTable.From(list).Write();
}
public int ReadNumberFromDisplay()
{
var selectedNumber = -1;
do
{
try
{
selectedNumber = Int32.Parse(Console.ReadLine() ?? string.Empty);
}
catch (Exception e)
{
// ignored
}
} while (selectedNumber < 0);
if (selectedNumber == 0)
{
throw new RestartException();
}
return selectedNumber;
}
}
}

View File

@ -0,0 +1,10 @@
using Automat.Models;
using Automat.Services.Interfaces;
namespace Automat.Services.Factories
{
public interface IPaymentGatewayFactory
{
public IPaymentGateway Create(PaymentOptionType type);
}
}

View File

@ -0,0 +1,25 @@
using Automat.Models;
using Automat.Services.Interfaces;
namespace Automat.Services.Factories
{
public class PaymentGatewayFactory : IPaymentGatewayFactory
{
private readonly IDisplayService _displayService;
public PaymentGatewayFactory(IDisplayService displayService)
{
_displayService = displayService;
}
public IPaymentGateway Create(PaymentOptionType type)
{
if (type == PaymentOptionType.Card)
{
return new CardPaymentGateway(_displayService);
}
return new CashPaymentGateway(_displayService);
}
}
}

View File

@ -0,0 +1,12 @@
using System.Threading.Tasks;
using Automat.Models;
namespace Automat.Services.Interfaces
{
public interface ICheckoutService
{
public Task ListPaymentOptions();
public Task<PaymentOption> PickPaymentOption();
public Task<bool> Checkout(PaymentOptionType type, int totalPrice);
}
}

View File

@ -0,0 +1,13 @@
using System.Collections.Generic;
namespace Automat.Services.Interfaces
{
public interface IDisplayService
{
void PrintSeparator();
void PrintSeparatorWithTitle(string title);
void WriteToDisplay(string message);
void WriteListToDisplay<T>(IEnumerable<T> list) where T : class;
int ReadNumberFromDisplay();
}
}

View File

@ -0,0 +1,9 @@
using System.Threading.Tasks;
namespace Automat.Services.Interfaces
{
public interface IPaymentGateway
{
public Task<bool> HandlePayment(int totalPrice);
}
}

View File

@ -0,0 +1,12 @@
using System.Threading.Tasks;
using Automat.Models;
namespace Automat.Services.Interfaces
{
public interface IShoppingService
{
public Task ListProducts();
public Task<Product> PickProduct();
public Task<bool> ServeMeal(Product product);
}
}

View File

@ -0,0 +1,67 @@
using System.Threading.Tasks;
using Automat.Models;
using Automat.Options;
using Automat.Repositories.Interfaces;
using Automat.Services.Interfaces;
using Microsoft.Extensions.Options;
namespace Automat.Services
{
public class ShoppingService : IShoppingService
{
private readonly IProductRepository _productRepository;
private readonly IDisplayService _displayService;
private readonly IOptions<AppSettings> _options;
public ShoppingService(IProductRepository productRepository, IDisplayService displayService, IOptions<AppSettings> options)
{
_productRepository = productRepository;
_displayService = displayService;
_options = options;
}
public async Task ListProducts()
{
_displayService.PrintSeparatorWithTitle("Wybierz produkt");
_displayService.WriteListToDisplay(await _productRepository.GetAllProducts());
}
public async Task<Product> PickProduct()
{
Product? product = null;
while (product is null)
{
var id = _displayService.ReadNumberFromDisplay();
product = await _productRepository.GetProduct(id);
if (product is not null && product.Stock <= 0)
{
_displayService.PrintSeparatorWithTitle("Brak produktu");
product = null;
}
}
return product;
}
public Task<bool> ServeMeal(Product product)
{
if (product.Type == ProductType.MainDish)
{
_displayService.PrintSeparatorWithTitle("Calling Staff Event");
}
var stock = product.DecreaseStock();
if (stock < _options.Value.LowStockThreshold)
{
_displayService.PrintSeparatorWithTitle("Restock Event");
}
_displayService.PrintSeparatorWithTitle("Serving Meal");
return Task.FromResult(true);
}
}
}

View File

@ -0,0 +1,13 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"AllowedHosts": "*",
"AppSettings": {
"LowStockThreshold": 3
}
}

0
s452708/README.md Normal file
View File