1
0

Add devcontainers

This commit is contained in:
Serghei Cebotari 2025-01-14 05:59:55 +00:00
parent 1b89c80965
commit 7b6f2fedf1
23 changed files with 1080 additions and 1045 deletions

View File

@ -0,0 +1,35 @@
// For format details, see https://aka.ms/devcontainer.json. For config options, see the
// README at: https://github.com/devcontainers/templates/tree/main/src/dotnet
{
"name": "C# (.NET)",
// Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile
"image": "mcr.microsoft.com/devcontainers/dotnet:1-8.0",
"customizations": {
"vscode": {
"extensions": [
"Ironcutter24.cscurlyformatter",
"ms-dotnettools.csdevkit"
]
}
}
// Features to add to the dev container. More info: https://containers.dev/features.
// "features": {},
// Use 'forwardPorts' to make a list of ports inside the container available locally.
// "forwardPorts": [5000, 5001],
// "portsAttributes": {
// "5001": {
// "protocol": "https"
// }
// }
// Use 'postCreateCommand' to run commands after the container is created.
// "postCreateCommand": "dotnet restore",
// Configure tool-specific properties.
// "customizations": {},
// Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root.
// "remoteUser": "root"
}

View File

@ -1,12 +1,12 @@
# directories # directories
**/bin/ **/bin/
**/obj/ **/obj/
**/out/ **/out/
# files # files
Dockerfile* Dockerfile*
**/*.trx **/*.trx
**/*.md **/*.md
**/*.ps1 **/*.ps1
**/*.cmd **/*.cmd
**/*.sh **/*.sh

968
.gitignore vendored
View File

@ -1,484 +1,484 @@
## Ignore Visual Studio temporary files, build results, and ## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons. ## files generated by popular Visual Studio add-ons.
## ##
## Get latest from `dotnet new gitignore` ## Get latest from `dotnet new gitignore`
# dotenv files # dotenv files
.env .env
# User-specific files # User-specific files
*.rsuser *.rsuser
*.suo *.suo
*.user *.user
*.userosscache *.userosscache
*.sln.docstates *.sln.docstates
# User-specific files (MonoDevelop/Xamarin Studio) # User-specific files (MonoDevelop/Xamarin Studio)
*.userprefs *.userprefs
# Mono auto generated files # Mono auto generated files
mono_crash.* mono_crash.*
# Build results # Build results
[Dd]ebug/ [Dd]ebug/
[Dd]ebugPublic/ [Dd]ebugPublic/
[Rr]elease/ [Rr]elease/
[Rr]eleases/ [Rr]eleases/
x64/ x64/
x86/ x86/
[Ww][Ii][Nn]32/ [Ww][Ii][Nn]32/
[Aa][Rr][Mm]/ [Aa][Rr][Mm]/
[Aa][Rr][Mm]64/ [Aa][Rr][Mm]64/
bld/ bld/
[Bb]in/ [Bb]in/
[Oo]bj/ [Oo]bj/
[Ll]og/ [Ll]og/
[Ll]ogs/ [Ll]ogs/
# Visual Studio 2015/2017 cache/options directory # Visual Studio 2015/2017 cache/options directory
.vs/ .vs/
# Uncomment if you have tasks that create the project's static files in wwwroot # Uncomment if you have tasks that create the project's static files in wwwroot
#wwwroot/ #wwwroot/
# Visual Studio 2017 auto generated files # Visual Studio 2017 auto generated files
Generated\ Files/ Generated\ Files/
# MSTest test Results # MSTest test Results
[Tt]est[Rr]esult*/ [Tt]est[Rr]esult*/
[Bb]uild[Ll]og.* [Bb]uild[Ll]og.*
# NUnit # NUnit
*.VisualState.xml *.VisualState.xml
TestResult.xml TestResult.xml
nunit-*.xml nunit-*.xml
# Build Results of an ATL Project # Build Results of an ATL Project
[Dd]ebugPS/ [Dd]ebugPS/
[Rr]eleasePS/ [Rr]eleasePS/
dlldata.c dlldata.c
# Benchmark Results # Benchmark Results
BenchmarkDotNet.Artifacts/ BenchmarkDotNet.Artifacts/
# .NET # .NET
project.lock.json project.lock.json
project.fragment.lock.json project.fragment.lock.json
artifacts/ artifacts/
# Tye # Tye
.tye/ .tye/
# ASP.NET Scaffolding # ASP.NET Scaffolding
ScaffoldingReadMe.txt ScaffoldingReadMe.txt
# StyleCop # StyleCop
StyleCopReport.xml StyleCopReport.xml
# Files built by Visual Studio # Files built by Visual Studio
*_i.c *_i.c
*_p.c *_p.c
*_h.h *_h.h
*.ilk *.ilk
*.meta *.meta
*.obj *.obj
*.iobj *.iobj
*.pch *.pch
*.pdb *.pdb
*.ipdb *.ipdb
*.pgc *.pgc
*.pgd *.pgd
*.rsp *.rsp
*.sbr *.sbr
*.tlb *.tlb
*.tli *.tli
*.tlh *.tlh
*.tmp *.tmp
*.tmp_proj *.tmp_proj
*_wpftmp.csproj *_wpftmp.csproj
*.log *.log
*.tlog *.tlog
*.vspscc *.vspscc
*.vssscc *.vssscc
.builds .builds
*.pidb *.pidb
*.svclog *.svclog
*.scc *.scc
# Chutzpah Test files # Chutzpah Test files
_Chutzpah* _Chutzpah*
# Visual C++ cache files # Visual C++ cache files
ipch/ ipch/
*.aps *.aps
*.ncb *.ncb
*.opendb *.opendb
*.opensdf *.opensdf
*.sdf *.sdf
*.cachefile *.cachefile
*.VC.db *.VC.db
*.VC.VC.opendb *.VC.VC.opendb
# Visual Studio profiler # Visual Studio profiler
*.psess *.psess
*.vsp *.vsp
*.vspx *.vspx
*.sap *.sap
# Visual Studio Trace Files # Visual Studio Trace Files
*.e2e *.e2e
# TFS 2012 Local Workspace # TFS 2012 Local Workspace
$tf/ $tf/
# Guidance Automation Toolkit # Guidance Automation Toolkit
*.gpState *.gpState
# ReSharper is a .NET coding add-in # ReSharper is a .NET coding add-in
_ReSharper*/ _ReSharper*/
*.[Rr]e[Ss]harper *.[Rr]e[Ss]harper
*.DotSettings.user *.DotSettings.user
# TeamCity is a build add-in # TeamCity is a build add-in
_TeamCity* _TeamCity*
# DotCover is a Code Coverage Tool # DotCover is a Code Coverage Tool
*.dotCover *.dotCover
# AxoCover is a Code Coverage Tool # AxoCover is a Code Coverage Tool
.axoCover/* .axoCover/*
!.axoCover/settings.json !.axoCover/settings.json
# Coverlet is a free, cross platform Code Coverage Tool # Coverlet is a free, cross platform Code Coverage Tool
coverage*.json coverage*.json
coverage*.xml coverage*.xml
coverage*.info coverage*.info
# Visual Studio code coverage results # Visual Studio code coverage results
*.coverage *.coverage
*.coveragexml *.coveragexml
# NCrunch # NCrunch
_NCrunch_* _NCrunch_*
.*crunch*.local.xml .*crunch*.local.xml
nCrunchTemp_* nCrunchTemp_*
# MightyMoose # MightyMoose
*.mm.* *.mm.*
AutoTest.Net/ AutoTest.Net/
# Web workbench (sass) # Web workbench (sass)
.sass-cache/ .sass-cache/
# Installshield output folder # Installshield output folder
[Ee]xpress/ [Ee]xpress/
# DocProject is a documentation generator add-in # DocProject is a documentation generator add-in
DocProject/buildhelp/ DocProject/buildhelp/
DocProject/Help/*.HxT DocProject/Help/*.HxT
DocProject/Help/*.HxC DocProject/Help/*.HxC
DocProject/Help/*.hhc DocProject/Help/*.hhc
DocProject/Help/*.hhk DocProject/Help/*.hhk
DocProject/Help/*.hhp DocProject/Help/*.hhp
DocProject/Help/Html2 DocProject/Help/Html2
DocProject/Help/html DocProject/Help/html
# Click-Once directory # Click-Once directory
publish/ publish/
# Publish Web Output # Publish Web Output
*.[Pp]ublish.xml *.[Pp]ublish.xml
*.azurePubxml *.azurePubxml
# Note: Comment the next line if you want to checkin your web deploy settings, # Note: Comment the next line if you want to checkin your web deploy settings,
# but database connection strings (with potential passwords) will be unencrypted # but database connection strings (with potential passwords) will be unencrypted
*.pubxml *.pubxml
*.publishproj *.publishproj
# Microsoft Azure Web App publish settings. Comment the next line if you want to # 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 # checkin your Azure Web App publish settings, but sensitive information contained
# in these scripts will be unencrypted # in these scripts will be unencrypted
PublishScripts/ PublishScripts/
# NuGet Packages # NuGet Packages
*.nupkg *.nupkg
# NuGet Symbol Packages # NuGet Symbol Packages
*.snupkg *.snupkg
# The packages folder can be ignored because of Package Restore # The packages folder can be ignored because of Package Restore
**/[Pp]ackages/* **/[Pp]ackages/*
# except build/, which is used as an MSBuild target. # except build/, which is used as an MSBuild target.
!**/[Pp]ackages/build/ !**/[Pp]ackages/build/
# Uncomment if necessary however generally it will be regenerated when needed # Uncomment if necessary however generally it will be regenerated when needed
#!**/[Pp]ackages/repositories.config #!**/[Pp]ackages/repositories.config
# NuGet v3's project.json files produces more ignorable files # NuGet v3's project.json files produces more ignorable files
*.nuget.props *.nuget.props
*.nuget.targets *.nuget.targets
# Microsoft Azure Build Output # Microsoft Azure Build Output
csx/ csx/
*.build.csdef *.build.csdef
# Microsoft Azure Emulator # Microsoft Azure Emulator
ecf/ ecf/
rcf/ rcf/
# Windows Store app package directories and files # Windows Store app package directories and files
AppPackages/ AppPackages/
BundleArtifacts/ BundleArtifacts/
Package.StoreAssociation.xml Package.StoreAssociation.xml
_pkginfo.txt _pkginfo.txt
*.appx *.appx
*.appxbundle *.appxbundle
*.appxupload *.appxupload
# Visual Studio cache files # Visual Studio cache files
# files ending in .cache can be ignored # files ending in .cache can be ignored
*.[Cc]ache *.[Cc]ache
# but keep track of directories ending in .cache # but keep track of directories ending in .cache
!?*.[Cc]ache/ !?*.[Cc]ache/
# Others # Others
ClientBin/ ClientBin/
~$* ~$*
*~ *~
*.dbmdl *.dbmdl
*.dbproj.schemaview *.dbproj.schemaview
*.jfm *.jfm
*.pfx *.pfx
*.publishsettings *.publishsettings
orleans.codegen.cs orleans.codegen.cs
# Including strong name files can present a security risk # Including strong name files can present a security risk
# (https://github.com/github/gitignore/pull/2483#issue-259490424) # (https://github.com/github/gitignore/pull/2483#issue-259490424)
#*.snk #*.snk
# Since there are multiple workflows, uncomment next line to ignore bower_components # Since there are multiple workflows, uncomment next line to ignore bower_components
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
#bower_components/ #bower_components/
# RIA/Silverlight projects # RIA/Silverlight projects
Generated_Code/ Generated_Code/
# Backup & report files from converting an old project file # Backup & report files from converting an old project file
# to a newer Visual Studio version. Backup files are not needed, # to a newer Visual Studio version. Backup files are not needed,
# because we have git ;-) # because we have git ;-)
_UpgradeReport_Files/ _UpgradeReport_Files/
Backup*/ Backup*/
UpgradeLog*.XML UpgradeLog*.XML
UpgradeLog*.htm UpgradeLog*.htm
ServiceFabricBackup/ ServiceFabricBackup/
*.rptproj.bak *.rptproj.bak
# SQL Server files # SQL Server files
*.mdf *.mdf
*.ldf *.ldf
*.ndf *.ndf
# Business Intelligence projects # Business Intelligence projects
*.rdl.data *.rdl.data
*.bim.layout *.bim.layout
*.bim_*.settings *.bim_*.settings
*.rptproj.rsuser *.rptproj.rsuser
*- [Bb]ackup.rdl *- [Bb]ackup.rdl
*- [Bb]ackup ([0-9]).rdl *- [Bb]ackup ([0-9]).rdl
*- [Bb]ackup ([0-9][0-9]).rdl *- [Bb]ackup ([0-9][0-9]).rdl
# Microsoft Fakes # Microsoft Fakes
FakesAssemblies/ FakesAssemblies/
# GhostDoc plugin setting file # GhostDoc plugin setting file
*.GhostDoc.xml *.GhostDoc.xml
# Node.js Tools for Visual Studio # Node.js Tools for Visual Studio
.ntvs_analysis.dat .ntvs_analysis.dat
node_modules/ node_modules/
# Visual Studio 6 build log # Visual Studio 6 build log
*.plg *.plg
# Visual Studio 6 workspace options file # Visual Studio 6 workspace options file
*.opt *.opt
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) # Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
*.vbw *.vbw
# Visual Studio 6 auto-generated project file (contains which files were open etc.) # Visual Studio 6 auto-generated project file (contains which files were open etc.)
*.vbp *.vbp
# Visual Studio 6 workspace and project file (working project files containing files to include in project) # Visual Studio 6 workspace and project file (working project files containing files to include in project)
*.dsw *.dsw
*.dsp *.dsp
# Visual Studio 6 technical files # Visual Studio 6 technical files
*.ncb *.ncb
*.aps *.aps
# Visual Studio LightSwitch build output # Visual Studio LightSwitch build output
**/*.HTMLClient/GeneratedArtifacts **/*.HTMLClient/GeneratedArtifacts
**/*.DesktopClient/GeneratedArtifacts **/*.DesktopClient/GeneratedArtifacts
**/*.DesktopClient/ModelManifest.xml **/*.DesktopClient/ModelManifest.xml
**/*.Server/GeneratedArtifacts **/*.Server/GeneratedArtifacts
**/*.Server/ModelManifest.xml **/*.Server/ModelManifest.xml
_Pvt_Extensions _Pvt_Extensions
# Paket dependency manager # Paket dependency manager
.paket/paket.exe .paket/paket.exe
paket-files/ paket-files/
# FAKE - F# Make # FAKE - F# Make
.fake/ .fake/
# CodeRush personal settings # CodeRush personal settings
.cr/personal .cr/personal
# Python Tools for Visual Studio (PTVS) # Python Tools for Visual Studio (PTVS)
__pycache__/ __pycache__/
*.pyc *.pyc
# Cake - Uncomment if you are using it # Cake - Uncomment if you are using it
# tools/** # tools/**
# !tools/packages.config # !tools/packages.config
# Tabs Studio # Tabs Studio
*.tss *.tss
# Telerik's JustMock configuration file # Telerik's JustMock configuration file
*.jmconfig *.jmconfig
# BizTalk build output # BizTalk build output
*.btp.cs *.btp.cs
*.btm.cs *.btm.cs
*.odx.cs *.odx.cs
*.xsd.cs *.xsd.cs
# OpenCover UI analysis results # OpenCover UI analysis results
OpenCover/ OpenCover/
# Azure Stream Analytics local run output # Azure Stream Analytics local run output
ASALocalRun/ ASALocalRun/
# MSBuild Binary and Structured Log # MSBuild Binary and Structured Log
*.binlog *.binlog
# NVidia Nsight GPU debugger configuration file # NVidia Nsight GPU debugger configuration file
*.nvuser *.nvuser
# MFractors (Xamarin productivity tool) working folder # MFractors (Xamarin productivity tool) working folder
.mfractor/ .mfractor/
# Local History for Visual Studio # Local History for Visual Studio
.localhistory/ .localhistory/
# Visual Studio History (VSHistory) files # Visual Studio History (VSHistory) files
.vshistory/ .vshistory/
# BeatPulse healthcheck temp database # BeatPulse healthcheck temp database
healthchecksdb healthchecksdb
# Backup folder for Package Reference Convert tool in Visual Studio 2017 # Backup folder for Package Reference Convert tool in Visual Studio 2017
MigrationBackup/ MigrationBackup/
# Ionide (cross platform F# VS Code tools) working folder # Ionide (cross platform F# VS Code tools) working folder
.ionide/ .ionide/
# Fody - auto-generated XML schema # Fody - auto-generated XML schema
FodyWeavers.xsd FodyWeavers.xsd
# VS Code files for those working on multiple tools # VS Code files for those working on multiple tools
.vscode/* .vscode/*
!.vscode/settings.json !.vscode/settings.json
!.vscode/tasks.json !.vscode/tasks.json
!.vscode/launch.json !.vscode/launch.json
!.vscode/extensions.json !.vscode/extensions.json
*.code-workspace *.code-workspace
# Local History for Visual Studio Code # Local History for Visual Studio Code
.history/ .history/
# Windows Installer files from build outputs # Windows Installer files from build outputs
*.cab *.cab
*.msi *.msi
*.msix *.msix
*.msm *.msm
*.msp *.msp
# JetBrains Rider # JetBrains Rider
*.sln.iml *.sln.iml
.idea .idea
## ##
## Visual studio for Mac ## Visual studio for Mac
## ##
# globs # globs
Makefile.in Makefile.in
*.userprefs *.userprefs
*.usertasks *.usertasks
config.make config.make
config.status config.status
aclocal.m4 aclocal.m4
install-sh install-sh
autom4te.cache/ autom4te.cache/
*.tar.gz *.tar.gz
tarballs/ tarballs/
test-results/ test-results/
# Mac bundle stuff # Mac bundle stuff
*.dmg *.dmg
*.app *.app
# content below from: https://github.com/github/gitignore/blob/master/Global/macOS.gitignore # content below from: https://github.com/github/gitignore/blob/master/Global/macOS.gitignore
# General # General
.DS_Store .DS_Store
.AppleDouble .AppleDouble
.LSOverride .LSOverride
# Icon must end with two \r # Icon must end with two \r
Icon Icon
# Thumbnails # Thumbnails
._* ._*
# Files that might appear in the root of a volume # Files that might appear in the root of a volume
.DocumentRevisions-V100 .DocumentRevisions-V100
.fseventsd .fseventsd
.Spotlight-V100 .Spotlight-V100
.TemporaryItems .TemporaryItems
.Trashes .Trashes
.VolumeIcon.icns .VolumeIcon.icns
.com.apple.timemachine.donotpresent .com.apple.timemachine.donotpresent
# Directories potentially created on remote AFP share # Directories potentially created on remote AFP share
.AppleDB .AppleDB
.AppleDesktop .AppleDesktop
Network Trash Folder Network Trash Folder
Temporary Items Temporary Items
.apdisk .apdisk
# content below from: https://github.com/github/gitignore/blob/master/Global/Windows.gitignore # content below from: https://github.com/github/gitignore/blob/master/Global/Windows.gitignore
# Windows thumbnail cache files # Windows thumbnail cache files
Thumbs.db Thumbs.db
ehthumbs.db ehthumbs.db
ehthumbs_vista.db ehthumbs_vista.db
# Dump file # Dump file
*.stackdump *.stackdump
# Folder config file # Folder config file
[Dd]esktop.ini [Dd]esktop.ini
# Recycle Bin used on file shares # Recycle Bin used on file shares
$RECYCLE.BIN/ $RECYCLE.BIN/
# Windows Installer files # Windows Installer files
*.cab *.cab
*.msi *.msi
*.msix *.msix
*.msm *.msm
*.msp *.msp
# Windows shortcuts # Windows shortcuts
*.lnk *.lnk
# Vim temporary swap files # Vim temporary swap files
*.swp *.swp

View File

@ -1,25 +1,25 @@
FROM --platform=$BUILDPLATFORM mcr.microsoft.com/dotnet/sdk:8.0-alpine AS build FROM --platform=$BUILDPLATFORM mcr.microsoft.com/dotnet/sdk:8.0-alpine AS build
ARG TARGETARCH ARG TARGETARCH
WORKDIR /source WORKDIR /source
COPY RhSolutions.SkuParser.Api/*.csproj . COPY RhSolutions.SkuParser.Api/*.csproj .
RUN dotnet restore -a $TARGETARCH RUN dotnet restore -a $TARGETARCH
COPY RhSolutions.SkuParser.Api/. . COPY RhSolutions.SkuParser.Api/. .
RUN dotnet publish -a $TARGETARCH --no-restore -o /app RUN dotnet publish -a $TARGETARCH --no-restore -o /app
FROM mcr.microsoft.com/dotnet/aspnet:8.0-alpine FROM mcr.microsoft.com/dotnet/aspnet:8.0-alpine
EXPOSE 8080 EXPOSE 8080
ENV \ ENV \
DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=false \ DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=false \
LC_ALL=ru_RU.UTF-8 \ LC_ALL=ru_RU.UTF-8 \
LANG=ru_RU.UTF-8 LANG=ru_RU.UTF-8
RUN apk add --no-cache \ RUN apk add --no-cache \
icu-data-full \ icu-data-full \
icu-libs icu-libs
WORKDIR /app WORKDIR /app
COPY --from=build /app . COPY --from=build /app .
USER $APP_UID USER $APP_UID
ENTRYPOINT ["./RhSolutions.SkuParser.Api"] ENTRYPOINT ["./RhSolutions.SkuParser.Api"]

View File

@ -1,6 +1,6 @@
# RhSolutions.SkuParser.Api # RhSolutions.SkuParser.Api
Сервис для парсинга актикулов РЕХАУ и их количества из файлов `xlsx`, `xlsm` и `csv` Сервис для парсинга актикулов РЕХАУ и их количества из файлов `xlsx`, `xlsm` и `csv`
Доступен через POST-метод `/api/Products/`. Отправлять файлы в `form-data`. Доступен через POST-метод `/api/Products/`. Отправлять файлы в `form-data`.
Количества одноименных артикулов в одном или нескольких файлах суммируются. Количества одноименных артикулов в одном или нескольких файлах суммируются.

View File

@ -1,48 +1,48 @@
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using RhSolutions.SkuParser.Models; using RhSolutions.SkuParser.Models;
using RhSolutions.SkuParser.Services; using RhSolutions.SkuParser.Services;
namespace RhSolutions.SkuParser.Controllers; namespace RhSolutions.SkuParser.Controllers;
[ApiController] [ApiController]
[Route("/api/[controller]")] [Route("/api/[controller]")]
public class ProductsController : ControllerBase public class ProductsController : ControllerBase
{ {
private IServiceProvider _provider; private IServiceProvider _provider;
private Dictionary<Product, double> _result; private Dictionary<Product, double> _result;
public ProductsController(IServiceProvider provider) public ProductsController(IServiceProvider provider)
{ {
_provider = provider; _provider = provider;
_result = new(); _result = new();
} }
[HttpPost] [HttpPost]
public IActionResult PostFiles() public IActionResult PostFiles()
{ {
IFormFileCollection files = Request.Form.Files; IFormFileCollection files = Request.Form.Files;
try try
{ {
foreach (var file in files) foreach (var file in files)
{ {
ISkuParser parser = _provider.GetRequiredKeyedService<ISkuParser>(file.ContentType); ISkuParser parser = _provider.GetRequiredKeyedService<ISkuParser>(file.ContentType);
IEnumerable<ProductQuantity> productQuantities = parser.ParseProducts(file); IEnumerable<ProductQuantity> productQuantities = parser.ParseProducts(file);
foreach (ProductQuantity pq in productQuantities) foreach (ProductQuantity pq in productQuantities)
{ {
if (_result.ContainsKey(pq.Product)) if (_result.ContainsKey(pq.Product))
{ {
_result[pq.Product] += pq.Quantity; _result[pq.Product] += pq.Quantity;
} }
else else
{ {
_result.Add(pq.Product, pq.Quantity); _result.Add(pq.Product, pq.Quantity);
} }
} }
} }
} }
catch (Exception ex) catch (Exception ex)
{ {
return BadRequest(error: $"{ex.Message}\n\n{ex.Source}\n{ex.StackTrace}"); return BadRequest(error: $"{ex.Message}\n\n{ex.Source}\n{ex.StackTrace}");
} }
return new JsonResult(_result.Select(x => new { Sku = x.Key.ToString(), Quantity = x.Value })); return new JsonResult(_result.Select(x => new { Sku = x.Key.ToString(), Quantity = x.Value }));
} }
} }

View File

@ -1,65 +1,65 @@
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
namespace RhSolutions.SkuParser.Models; namespace RhSolutions.SkuParser.Models;
public record Product public record Product
{ {
/// <summary> /// <summary>
/// Артикул РЕХАУ в заданном формате /// Артикул РЕХАУ в заданном формате
/// </summary> /// </summary>
public required string Sku public required string Sku
{ {
get => _sku; get => _sku;
set set
{ {
_sku = IsValudSku(value) _sku = IsValudSku(value)
? value ? value
: throw new ArgumentException("$Неверный артикул: {value}"); : throw new ArgumentException("$Неверный артикул: {value}");
} }
} }
private string _sku = string.Empty; private string _sku = string.Empty;
private const string _parsePattern = @"(?<Lead>[1\s]|^|\b)(?<Article>\d{6})(?<Delimiter>[\s13-])(?<Variant>\d{3})(\b|$)"; private const string _parsePattern = @"(?<Lead>[1\s]|^|\b)(?<Article>\d{6})(?<Delimiter>[\s13-])(?<Variant>\d{3})(\b|$)";
private const string _validnessPattern = @"^1\d{6}[1|3]\d{3}$"; private const string _validnessPattern = @"^1\d{6}[1|3]\d{3}$";
private static bool IsValudSku(string value) private static bool IsValudSku(string value)
{ {
return Regex.IsMatch(value.Trim(), _validnessPattern); return Regex.IsMatch(value.Trim(), _validnessPattern);
} }
private static string GetSku(Match match) private static string GetSku(Match match)
{ {
string lead = match.Groups["Lead"].Value; string lead = match.Groups["Lead"].Value;
string article = match.Groups["Article"].Value; string article = match.Groups["Article"].Value;
string delimiter = match.Groups["Delimiter"].Value; string delimiter = match.Groups["Delimiter"].Value;
string variant = match.Groups["Variant"].Value; string variant = match.Groups["Variant"].Value;
if (lead != "1" && delimiter == "-") if (lead != "1" && delimiter == "-")
{ {
return $"1{article}1{variant}"; return $"1{article}1{variant}";
} }
else else
{ {
return $"{lead}{article}{delimiter}{variant}"; return $"{lead}{article}{delimiter}{variant}";
} }
} }
/// <summary> /// <summary>
/// Проверка строки на наличие в ней артикула РЕХАУ /// Проверка строки на наличие в ней артикула РЕХАУ
/// </summary> /// </summary>
/// <param name="value">Входная строка для проверки</param> /// <param name="value">Входная строка для проверки</param>
/// <param name="product">Артикул, если найден. null - если нет</param> /// <param name="product">Артикул, если найден. null - если нет</param>
/// <returns>Если артикул в строке есть возвращает true, Если нет - false</returns> /// <returns>Если артикул в строке есть возвращает true, Если нет - false</returns>
public static bool TryParse(string value, out Product? product) public static bool TryParse(string value, out Product? product)
{ {
product = null; product = null;
MatchCollection matches = Regex.Matches(value, _parsePattern); MatchCollection matches = Regex.Matches(value, _parsePattern);
if (matches.Count == 0) if (matches.Count == 0)
{ {
return false; return false;
} }
string sku = GetSku(matches.First()); string sku = GetSku(matches.First());
product = new Product() { Sku = sku }; product = new Product() { Sku = sku };
return true; return true;
} }
public override int GetHashCode() => Sku.GetHashCode(); public override int GetHashCode() => Sku.GetHashCode();
public override string ToString() => Sku; public override string ToString() => Sku;
} }

View File

@ -1,30 +1,30 @@
using CsvHelper.Configuration.Attributes; using CsvHelper.Configuration.Attributes;
namespace RhSolutions.SkuParser.Models; namespace RhSolutions.SkuParser.Models;
public class ProductQuantity public class ProductQuantity
{ {
[Index(0)] [Index(0)]
public required Product Product { get; set; } public required Product Product { get; set; }
[Index(1)] [Index(1)]
public required double Quantity { get; set; } public required double Quantity { get; set; }
public override bool Equals(object? obj) public override bool Equals(object? obj)
{ {
if (obj == null || GetType() != obj.GetType()) if (obj == null || GetType() != obj.GetType())
{ {
return false; return false;
} }
ProductQuantity other = (ProductQuantity)obj; ProductQuantity other = (ProductQuantity)obj;
return Product == other.Product && return Product == other.Product &&
Quantity == other.Quantity; Quantity == other.Quantity;
} }
public override int GetHashCode() public override int GetHashCode()
{ {
HashCode hash = new(); HashCode hash = new();
hash.Add(Product); hash.Add(Product);
hash.Add(Quantity); hash.Add(Quantity);
return hash.ToHashCode(); return hash.ToHashCode();
} }
} }

View File

@ -1,12 +1,12 @@
using RhSolutions.SkuParser.Services; using RhSolutions.SkuParser.Services;
var builder = WebApplication.CreateBuilder(args); var builder = WebApplication.CreateBuilder(args);
builder.Services builder.Services
.AddKeyedScoped<ISkuParser, CsvParser>("text/csv") .AddKeyedScoped<ISkuParser, CsvParser>("text/csv")
.AddKeyedScoped<ISkuParser, ExcelParser>("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet") .AddKeyedScoped<ISkuParser, ExcelParser>("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
.AddKeyedScoped<ISkuParser, ExcelParser>("application/vnd.ms-excel.sheet.macroenabled.12"); .AddKeyedScoped<ISkuParser, ExcelParser>("application/vnd.ms-excel.sheet.macroenabled.12");
builder.Services.AddControllers(); builder.Services.AddControllers();
var app = builder.Build(); var app = builder.Build();
app.MapControllers(); app.MapControllers();
app.Run(); app.Run();

View File

@ -1,29 +1,29 @@
{ {
"$schema": "http://json.schemastore.org/launchsettings.json", "$schema": "http://json.schemastore.org/launchsettings.json",
"iisSettings": { "iisSettings": {
"windowsAuthentication": false, "windowsAuthentication": false,
"anonymousAuthentication": true, "anonymousAuthentication": true,
"iisExpress": { "iisExpress": {
"applicationUrl": "http://localhost:8080", "applicationUrl": "http://localhost:8080",
"sslPort": 44355 "sslPort": 44355
} }
}, },
"profiles": { "profiles": {
"http": { "http": {
"commandName": "Project", "commandName": "Project",
"dotnetRunMessages": true, "dotnetRunMessages": true,
"launchBrowser": false, "launchBrowser": false,
"applicationUrl": "http://localhost:8080", "applicationUrl": "http://localhost:8080",
"environmentVariables": { "environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development" "ASPNETCORE_ENVIRONMENT": "Development"
} }
}, },
"IIS Express": { "IIS Express": {
"commandName": "IISExpress", "commandName": "IISExpress",
"launchBrowser": true, "launchBrowser": true,
"environmentVariables": { "environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development" "ASPNETCORE_ENVIRONMENT": "Development"
} }
} }
} }
} }

View File

@ -1,14 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk.Web"> <Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net8.0</TargetFramework> <TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings> <ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="ClosedXML" Version="0.102.3" /> <PackageReference Include="ClosedXML" Version="0.102.3" />
<PackageReference Include="CsvHelper" Version="33.0.1" /> <PackageReference Include="CsvHelper" Version="33.0.1" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@ -1,24 +1,24 @@
using System.Globalization; using System.Globalization;
using CsvHelper; using CsvHelper;
using CsvHelper.Configuration; using CsvHelper.Configuration;
using RhSolutions.SkuParser.Models; using RhSolutions.SkuParser.Models;
namespace RhSolutions.SkuParser.Services; namespace RhSolutions.SkuParser.Services;
/// <summary> /// <summary>
/// Парсер артикулов и их количества из файлов *.csv /// Парсер артикулов и их количества из файлов *.csv
/// </summary> /// </summary>
public class CsvParser : ISkuParser public class CsvParser : ISkuParser
{ {
public IEnumerable<ProductQuantity> ParseProducts(IFormFile file) public IEnumerable<ProductQuantity> ParseProducts(IFormFile file)
{ {
using StreamReader reader = new(file.OpenReadStream()); using StreamReader reader = new(file.OpenReadStream());
var config = new CsvConfiguration(CultureInfo.GetCultureInfo("ru-RU")) var config = new CsvConfiguration(CultureInfo.GetCultureInfo("ru-RU"))
{ {
HasHeaderRecord = false, HasHeaderRecord = false,
}; };
using CsvReader csvReader = new(reader, config); using CsvReader csvReader = new(reader, config);
return csvReader.GetRecords<ProductQuantity>().ToList(); return csvReader.GetRecords<ProductQuantity>().ToList();
} }
} }

View File

@ -1,76 +1,76 @@
using ClosedXML.Excel; using ClosedXML.Excel;
using RhSolutions.SkuParser.Models; using RhSolutions.SkuParser.Models;
namespace RhSolutions.SkuParser.Services; namespace RhSolutions.SkuParser.Services;
public class ExcelParser : ISkuParser public class ExcelParser : ISkuParser
{ {
public IEnumerable<ProductQuantity> ParseProducts(IFormFile file) public IEnumerable<ProductQuantity> ParseProducts(IFormFile file)
{ {
using XLWorkbook workbook = new(file.OpenReadStream()); using XLWorkbook workbook = new(file.OpenReadStream());
IXLWorksheet ws = workbook.Worksheet(1); IXLWorksheet ws = workbook.Worksheet(1);
var leftTop = ws.FirstCellUsed()?.Address; var leftTop = ws.FirstCellUsed()?.Address;
var rightBottom = ws.LastCellUsed()?.Address; var rightBottom = ws.LastCellUsed()?.Address;
if (new object?[] { leftTop, rightBottom }.Any(x => x == null)) if (new object?[] { leftTop, rightBottom }.Any(x => x == null))
{ {
throw new ArgumentException($"Таблица пуста: {file.FileName}"); throw new ArgumentException($"Таблица пуста: {file.FileName}");
} }
var lookupRange = ws.Range(leftTop, rightBottom).RangeUsed(); var lookupRange = ws.Range(leftTop, rightBottom).RangeUsed();
var columns = lookupRange.Columns(); var columns = lookupRange.Columns();
var skuColumnQuantity = columns var skuColumnQuantity = columns
.Select(column => new .Select(column => new
{ {
Column = column, Column = column,
Products = column.CellsUsed() Products = column.CellsUsed()
.Select(cell => !cell.HasFormula && Product.TryParse(cell.Value.ToString(), out Product? p) ? p : null) .Select(cell => !cell.HasFormula && Product.TryParse(cell.Value.ToString(), out Product? p) ? p : null)
}) })
.Select(c => new { c.Column, SkuCount = c.Products.Count(p => p != null) }) .Select(c => new { c.Column, SkuCount = c.Products.Count(p => p != null) })
.Aggregate((l, r) => l.SkuCount > r.SkuCount ? l : r); .Aggregate((l, r) => l.SkuCount > r.SkuCount ? l : r);
var skuColumn = skuColumnQuantity.SkuCount > 0 ? skuColumnQuantity.Column : null; var skuColumn = skuColumnQuantity.SkuCount > 0 ? skuColumnQuantity.Column : null;
if (skuColumn == null) if (skuColumn == null)
{ {
throw new ArgumentException($"Столбец с артикулом не определен: {file.FileName}"); throw new ArgumentException($"Столбец с артикулом не определен: {file.FileName}");
} }
var quantityColumn = lookupRange.Columns().Skip(skuColumn.ColumnNumber()) var quantityColumn = lookupRange.Columns().Skip(skuColumn.ColumnNumber())
.Select(column => new .Select(column => new
{ {
Column = column, Column = column,
IsColumnWithNumbers = column.CellsUsed() IsColumnWithNumbers = column.CellsUsed()
.Count(cell => cell.Value.IsNumber == true) > column.CellsUsed().Count() / 4 .Count(cell => cell.Value.IsNumber == true) > column.CellsUsed().Count() / 4
}) })
.First(x => x.IsColumnWithNumbers) .First(x => x.IsColumnWithNumbers)
.Column; .Column;
if (quantityColumn == null) if (quantityColumn == null)
{ {
throw new ArgumentException($"Столбец с количеством не определен: {file.FileName}"); throw new ArgumentException($"Столбец с количеством не определен: {file.FileName}");
} }
List<ProductQuantity> result = new(); List<ProductQuantity> result = new();
var rows = quantityColumn.CellsUsed().Select(x => x.Address.RowNumber); var rows = quantityColumn.CellsUsed().Select(x => x.Address.RowNumber);
foreach (var row in rows) foreach (var row in rows)
{ {
var quantity = quantityColumn.Cell(row).Value; var quantity = quantityColumn.Cell(row).Value;
var sku = skuColumn.Cell(row).Value; var sku = skuColumn.Cell(row).Value;
if (quantity.IsNumber if (quantity.IsNumber
&& Product.TryParse(sku.ToString(), out Product? p)) && Product.TryParse(sku.ToString(), out Product? p))
{ {
ProductQuantity pq = new() ProductQuantity pq = new()
{ {
Product = p!, Product = p!,
Quantity = quantity.GetNumber() Quantity = quantity.GetNumber()
}; };
result.Add(pq); result.Add(pq);
} }
} }
return result; return result;
} }
} }

View File

@ -1,7 +1,7 @@
using RhSolutions.SkuParser.Models; using RhSolutions.SkuParser.Models;
namespace RhSolutions.SkuParser.Services; namespace RhSolutions.SkuParser.Services;
public interface ISkuParser public interface ISkuParser
{ {
public IEnumerable<ProductQuantity> ParseProducts(IFormFile file); public IEnumerable<ProductQuantity> ParseProducts(IFormFile file);
} }

View File

@ -1,8 +1,8 @@
{ {
"Logging": { "Logging": {
"LogLevel": { "LogLevel": {
"Default": "Information", "Default": "Information",
"Microsoft.AspNetCore": "Warning" "Microsoft.AspNetCore": "Warning"
} }
} }
} }

View File

@ -1,9 +1,9 @@
{ {
"Logging": { "Logging": {
"LogLevel": { "LogLevel": {
"Default": "Information", "Default": "Information",
"Microsoft.AspNetCore": "Warning" "Microsoft.AspNetCore": "Warning"
} }
}, },
"AllowedHosts": "*" "AllowedHosts": "*"
} }

View File

@ -1,48 +1,48 @@
using RhSolutions.SkuParser.Services; using RhSolutions.SkuParser.Services;
namespace RhSolutions.SkuParser.Tests; namespace RhSolutions.SkuParser.Tests;
public class ExcelParserTests public class ExcelParserTests
{ {
private static readonly List<ProductQuantity> _expected = new() private static readonly List<ProductQuantity> _expected = new()
{ {
new ProductQuantity() {Product= new Product() {Sku = "11303703100"}, Quantity = 2129.5}, new ProductQuantity() {Product= new Product() {Sku = "11303703100"}, Quantity = 2129.5},
new ProductQuantity() {Product= new Product() {Sku = "11303803100"}, Quantity = 503}, new ProductQuantity() {Product= new Product() {Sku = "11303803100"}, Quantity = 503},
new ProductQuantity() {Product= new Product() {Sku = "11303903050"}, Quantity = 52}, new ProductQuantity() {Product= new Product() {Sku = "11303903050"}, Quantity = 52},
new ProductQuantity() {Product= new Product() {Sku = "11080011001"}, Quantity = 2154}, new ProductQuantity() {Product= new Product() {Sku = "11080011001"}, Quantity = 2154},
new ProductQuantity() {Product= new Product() {Sku = "11080021001"}, Quantity = 134}, new ProductQuantity() {Product= new Product() {Sku = "11080021001"}, Quantity = 134},
new ProductQuantity() {Product= new Product() {Sku = "11080031001"}, Quantity = 6}, new ProductQuantity() {Product= new Product() {Sku = "11080031001"}, Quantity = 6},
new ProductQuantity() {Product= new Product() {Sku = "11080311001"}, Quantity = 462}, new ProductQuantity() {Product= new Product() {Sku = "11080311001"}, Quantity = 462},
new ProductQuantity() {Product= new Product() {Sku = "11080611001"}, Quantity = 38}, new ProductQuantity() {Product= new Product() {Sku = "11080611001"}, Quantity = 38},
new ProductQuantity() {Product= new Product() {Sku = "11080811001"}, Quantity = 24}, new ProductQuantity() {Product= new Product() {Sku = "11080811001"}, Quantity = 24},
new ProductQuantity() {Product= new Product() {Sku = "11080831001"}, Quantity = 2}, new ProductQuantity() {Product= new Product() {Sku = "11080831001"}, Quantity = 2},
}; };
[TestCase("simple.xlsx")] [TestCase("simple.xlsx")]
[TestCase("simpleWithNames.xlsx")] [TestCase("simpleWithNames.xlsx")]
[TestCase("withHeader.xlsx")] [TestCase("withHeader.xlsx")]
[TestCase("withHeaderAndGarbage.xlsx")] [TestCase("withHeaderAndGarbage.xlsx")]
[TestCase("twoTables.xlsx")] [TestCase("twoTables.xlsx")]
[TestCase("rhSolutionsBsTable.xlsx")] [TestCase("rhSolutionsBsTable.xlsx")]
[TestCase("simpleWithFormulas.xlsx")] [TestCase("simpleWithFormulas.xlsx")]
public void XlsxTests(string filename) public void XlsxTests(string filename)
{ {
var mockFile = FormFileUtil.GetMockFormFile(filename); var mockFile = FormFileUtil.GetMockFormFile(filename);
var parser = new ExcelParser(); var parser = new ExcelParser();
var actual = parser.ParseProducts(mockFile.Object); var actual = parser.ParseProducts(mockFile.Object);
Assert.That(actual.Count, Is.EqualTo(_expected.Count())); Assert.That(actual.Count, Is.EqualTo(_expected.Count()));
CollectionAssert.AllItemsAreInstancesOfType(actual, typeof(ProductQuantity)); CollectionAssert.AllItemsAreInstancesOfType(actual, typeof(ProductQuantity));
CollectionAssert.AreEqual(_expected, actual); CollectionAssert.AreEqual(_expected, actual);
} }
[TestCase("simple.csv")] [TestCase("simple.csv")]
public void CsvTests(string filename) public void CsvTests(string filename)
{ {
var mockFile = FormFileUtil.GetMockFormFile(filename); var mockFile = FormFileUtil.GetMockFormFile(filename);
var parser = new CsvParser(); var parser = new CsvParser();
var actual = parser.ParseProducts(mockFile.Object); var actual = parser.ParseProducts(mockFile.Object);
Assert.That(actual.Count, Is.EqualTo(_expected.Count())); Assert.That(actual.Count, Is.EqualTo(_expected.Count()));
CollectionAssert.AllItemsAreInstancesOfType(actual, typeof(ProductQuantity)); CollectionAssert.AllItemsAreInstancesOfType(actual, typeof(ProductQuantity));
CollectionAssert.AreEqual(_expected, actual); CollectionAssert.AreEqual(_expected, actual);
} }
} }

View File

@ -1,17 +1,17 @@
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Moq; using Moq;
namespace RhSolutions.SkuParser.Tests; namespace RhSolutions.SkuParser.Tests;
public static class FormFileUtil public static class FormFileUtil
{ {
public static Mock<IFormFile> GetMockFormFile(string workbookName) public static Mock<IFormFile> GetMockFormFile(string workbookName)
{ {
string filepath = "./../../../Workbooks/" + workbookName; string filepath = "./../../../Workbooks/" + workbookName;
var mockFile = new Mock<IFormFile>(); var mockFile = new Mock<IFormFile>();
var memoryStream = new MemoryStream([.. File.ReadAllBytes(filepath)]); var memoryStream = new MemoryStream([.. File.ReadAllBytes(filepath)]);
mockFile.Setup(x => x.OpenReadStream()) mockFile.Setup(x => x.OpenReadStream())
.Returns(memoryStream); .Returns(memoryStream);
return mockFile; return mockFile;
} }
} }

View File

@ -1,2 +1,2 @@
global using NUnit.Framework; global using NUnit.Framework;
global using RhSolutions.SkuParser.Models; global using RhSolutions.SkuParser.Models;

View File

@ -1,75 +1,75 @@
namespace RhSolutions.SkuParser.Tests; namespace RhSolutions.SkuParser.Tests;
public class ProductTests public class ProductTests
{ {
[TestCase("12222221001")] [TestCase("12222221001")]
[TestCase("12222223001")] [TestCase("12222223001")]
[TestCase("160001-001")] [TestCase("160001-001")]
public void SimpleParse(string value) public void SimpleParse(string value)
{ {
Assert.True(Product.TryParse(value, out _)); Assert.True(Product.TryParse(value, out _));
} }
[TestCase("string 12222221001")] [TestCase("string 12222221001")]
[TestCase("12222223001 string")] [TestCase("12222223001 string")]
[TestCase("string 160001-001")] [TestCase("string 160001-001")]
[TestCase("160001-001 string ")] [TestCase("160001-001 string ")]
[TestCase("11096641001 Трубка РЕХАУ из. нерж. стали для подкл. радиатора, Г-образная 16/250")] [TestCase("11096641001 Трубка РЕХАУ из. нерж. стали для подкл. радиатора, Г-образная 16/250")]
public void AdvancedParse(string value) public void AdvancedParse(string value)
{ {
Assert.True(Product.TryParse(value, out _)); Assert.True(Product.TryParse(value, out _));
} }
[TestCase("11600011001")] [TestCase("11600011001")]
[TestCase("160001-001")] [TestCase("160001-001")]
public void ProductIsCorrect(string value) public void ProductIsCorrect(string value)
{ {
if (Product.TryParse(value, out Product? product)) if (Product.TryParse(value, out Product? product))
{ {
Assert.That(product!.Sku, Is.EqualTo("11600011001")); Assert.That(product!.Sku, Is.EqualTo("11600011001"));
} }
else else
{ {
Assert.Fail($"Parsing failed on {value}"); Assert.Fail($"Parsing failed on {value}");
} }
} }
[TestCase("1222222001")] [TestCase("1222222001")]
[TestCase("12222225001")] [TestCase("12222225001")]
public void NotParses(string value) public void NotParses(string value)
{ {
Assert.False(Product.TryParse(value, out _)); Assert.False(Product.TryParse(value, out _));
} }
[Test] [Test]
public void ProductEquality() public void ProductEquality()
{ {
string value = "12222223001"; string value = "12222223001";
Product.TryParse(value, out Product? first); Product.TryParse(value, out Product? first);
Product.TryParse(value, out Product? second); Product.TryParse(value, out Product? second);
if (first == null || second == null) if (first == null || second == null)
{ {
Assert.Fail($"Parsing failed on {value}"); Assert.Fail($"Parsing failed on {value}");
} }
else else
{ {
Assert.True(first.Equals(second)); Assert.True(first.Equals(second));
} }
} }
[Test] [Test]
public void HashTest() public void HashTest()
{ {
string value = "12222223001"; string value = "12222223001";
HashSet<Product> set = new(); HashSet<Product> set = new();
if (Product.TryParse(value, out var product)) if (Product.TryParse(value, out var product))
{ {
set.Add(product!); set.Add(product!);
} }
else else
{ {
Assert.Fail($"Parsing failed on {value}"); Assert.Fail($"Parsing failed on {value}");
} }
Assert.True(set.Contains(product!)); Assert.True(set.Contains(product!));
} }
} }

View File

@ -1,25 +1,25 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net8.0</TargetFramework> <TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings> <ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<IsPackable>false</IsPackable> <IsPackable>false</IsPackable>
<IsTestProject>true</IsTestProject> <IsTestProject>true</IsTestProject>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.6.0" /> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.6.0" />
<PackageReference Include="Moq" Version="4.20.70" /> <PackageReference Include="Moq" Version="4.20.70" />
<PackageReference Include="NUnit" Version="3.13.3" /> <PackageReference Include="NUnit" Version="3.13.3" />
<PackageReference Include="NUnit3TestAdapter" Version="4.2.1" /> <PackageReference Include="NUnit3TestAdapter" Version="4.2.1" />
<PackageReference Include="NUnit.Analyzers" Version="3.6.1" /> <PackageReference Include="NUnit.Analyzers" Version="3.6.1" />
<PackageReference Include="coverlet.collector" Version="6.0.0" /> <PackageReference Include="coverlet.collector" Version="6.0.0" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\RhSolutions.SkuParser.Api\RhSolutions.SkuParser.Api.csproj" /> <ProjectReference Include="..\RhSolutions.SkuParser.Api\RhSolutions.SkuParser.Api.csproj" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@ -1,10 +1,10 @@
11303703100;2129,5 11303703100;2129,5
11303803100;503 11303803100;503
11303903050;52 11303903050;52
11080011001;2154 11080011001;2154
11080021001;134 11080021001;134
11080031001;6 11080031001;6
11080311001;462 11080311001;462
11080611001;38 11080611001;38
11080811001;24 11080811001;24
11080831001;2 11080831001;2

1 11303703100 2129,5
2 11303803100 503
3 11303903050 52
4 11080011001 2154
5 11080021001 134
6 11080031001 6
7 11080311001 462
8 11080611001 38
9 11080811001 24
10 11080831001 2

View File

@ -1,28 +1,28 @@
 
Microsoft Visual Studio Solution File, Format Version 12.00 Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17 # Visual Studio Version 17
VisualStudioVersion = 17.0.31903.59 VisualStudioVersion = 17.0.31903.59
MinimumVisualStudioVersion = 10.0.40219.1 MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RhSolutions.SkuParser.Api", "RhSolutions.SkuParser.Api\RhSolutions.SkuParser.Api.csproj", "{5178E712-F984-48F4-9C68-6DC8CCAA4053}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RhSolutions.SkuParser.Api", "RhSolutions.SkuParser.Api\RhSolutions.SkuParser.Api.csproj", "{5178E712-F984-48F4-9C68-6DC8CCAA4053}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RhSolutions.SkuParser.Tests", "RhSolutions.SkuParser.Tests\RhSolutions.SkuParser.Tests.csproj", "{8EB147E6-E7B9-42AB-B634-BA2EA1A7A542}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RhSolutions.SkuParser.Tests", "RhSolutions.SkuParser.Tests\RhSolutions.SkuParser.Tests.csproj", "{8EB147E6-E7B9-42AB-B634-BA2EA1A7A542}"
EndProject EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU Release|Any CPU = Release|Any CPU
EndGlobalSection EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE
EndGlobalSection EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution GlobalSection(ProjectConfigurationPlatforms) = postSolution
{5178E712-F984-48F4-9C68-6DC8CCAA4053}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {5178E712-F984-48F4-9C68-6DC8CCAA4053}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{5178E712-F984-48F4-9C68-6DC8CCAA4053}.Debug|Any CPU.Build.0 = Debug|Any CPU {5178E712-F984-48F4-9C68-6DC8CCAA4053}.Debug|Any CPU.Build.0 = Debug|Any CPU
{5178E712-F984-48F4-9C68-6DC8CCAA4053}.Release|Any CPU.ActiveCfg = Release|Any CPU {5178E712-F984-48F4-9C68-6DC8CCAA4053}.Release|Any CPU.ActiveCfg = Release|Any CPU
{5178E712-F984-48F4-9C68-6DC8CCAA4053}.Release|Any CPU.Build.0 = Release|Any CPU {5178E712-F984-48F4-9C68-6DC8CCAA4053}.Release|Any CPU.Build.0 = Release|Any CPU
{8EB147E6-E7B9-42AB-B634-BA2EA1A7A542}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {8EB147E6-E7B9-42AB-B634-BA2EA1A7A542}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{8EB147E6-E7B9-42AB-B634-BA2EA1A7A542}.Debug|Any CPU.Build.0 = Debug|Any CPU {8EB147E6-E7B9-42AB-B634-BA2EA1A7A542}.Debug|Any CPU.Build.0 = Debug|Any CPU
{8EB147E6-E7B9-42AB-B634-BA2EA1A7A542}.Release|Any CPU.ActiveCfg = Release|Any CPU {8EB147E6-E7B9-42AB-B634-BA2EA1A7A542}.Release|Any CPU.ActiveCfg = Release|Any CPU
{8EB147E6-E7B9-42AB-B634-BA2EA1A7A542}.Release|Any CPU.Build.0 = Release|Any CPU {8EB147E6-E7B9-42AB-B634-BA2EA1A7A542}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection EndGlobalSection
EndGlobal EndGlobal