first try

This commit is contained in:
2025-11-13 07:51:17 +04:00
parent e99f67905d
commit 5505f5152c
10 changed files with 722 additions and 0 deletions

289
.gitignore vendored Normal file
View File

@@ -0,0 +1,289 @@
## 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
*.suo
*.user
*.userosscache
*.sln.docstates
# User-specific files (MonoDevelop/Xamarin Studio)
*.userprefs
# Build results
[Dd]ebug/
[Dd]ebugPublic/
[Rr]elease/
x64/
x86/
bld/
[Bb]in/
[Oo]bj/
[Ll]og/
# Visual Studio 2015 cache/options directory
.vs/
# Uncomment if you have tasks that create the project's static files in wwwroot
#wwwroot/
# 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
# .NET Core
project.lock.json
project.fragment.lock.json
artifacts/
**/Properties/launchSettings.json
# VS Code
.vscode/
*_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
*.VC.db
*.VC.VC.opendb
# 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
# 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
# TODO: Comment the next line if you want to checkin your web deploy settings
# but database connection strings (with potential passwords) will be unencrypted
*.pubxml
*.publishproj
# Microsoft Azure Web App publish settings. Comment the next line if you want to
# checkin your Azure Web App publish settings, but sensitive information contained
# in these scripts will be unencrypted
PublishScripts/
# NuGet Packages
*.nupkg
# The packages folder can be ignored because of Package Restore
**/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
# 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
# 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
# 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
# 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
node_modules/
# Typescript v1 declaration files
typings/
# Visual Studio 6 build log
*.plg
# Visual Studio 6 workspace options file
*.opt
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
*.vbw
# Visual Studio LightSwitch build output
**/*.HTMLClient/GeneratedArtifacts
**/*.DesktopClient/GeneratedArtifacts
**/*.DesktopClient/ModelManifest.xml
**/*.Server/GeneratedArtifacts
**/*.Server/ModelManifest.xml
_Pvt_Extensions
# Paket dependency manager
.paket/paket.exe
paket-files/
# FAKE - F# Make
.fake/
# JetBrains Rider
.idea/
*.sln.iml
# CodeRush
.cr/
# Python Tools for Visual Studio (PTVS)
__pycache__/
*.pyc
# Cake - Uncomment if you are using it
# tools/**
# !tools/packages.config
# Telerik's JustMock configuration file
*.jmconfig
# BizTalk build output
*.btp.cs
*.btm.cs
*.odx.cs
*.xsd.cs

View File

@@ -0,0 +1,84 @@
using System;
namespace su.divan2000.SalaryApp.Models
{
public class SalaryData
{
// Входные данные
public double SalaryBase { get; private set; }
public int DaysWorked { get; private set; }
public int NightShifts { get; private set; }
public int OvertimeHours { get; private set; }
public int ExperienceYears { get; private set; }
public bool HasViolations { get; private set; }
// Результаты расчёта
public double Bonus { get; private set; }
public double Penalty { get; private set; }
public double Tax { get; private set; }
public double TotalSalary { get; private set; }
public DateTime CalculationDate { get; private set; }
// Константы расчёта
private const double NightShiftBonusRate = 0.20; // 20% от дневной ставки
private const double OvertimeRate = 300.0; // руб/час
private const double TaxRate = 0.13; // 13% НДФЛ
private const double ViolationPenaltyRate = 0.15; // 15% штраф при нарушениях
public SalaryData(double salaryBase, int daysWorked, int nightShifts, int overtimeHours, int experienceYears, bool hasViolations)
{
if (daysWorked <= 0)
throw new ArgumentException("Количество рабочих дней должно быть больше нуля.");
if (nightShifts < 0 || nightShifts > daysWorked)
throw new ArgumentException("Ночных смен не может быть больше отработанных дней и меньше нуля.");
if (overtimeHours < 0)
throw new ArgumentException("Сверхурочные часы не могут быть отрицательными.");
if (salaryBase <= 0)
throw new ArgumentException("Оклад должен быть положительным.");
SalaryBase = salaryBase;
DaysWorked = daysWorked;
NightShifts = nightShifts;
OvertimeHours = overtimeHours;
ExperienceYears = experienceYears;
HasViolations = hasViolations;
}
public void CalculateSalary()
{
double dailyRate = SalaryBase / DaysWorked;
double nightBonus = dailyRate * NightShifts * NightShiftBonusRate;
double overtimePay = OvertimeHours * OvertimeRate;
double experienceBonus = 0;
if (ExperienceYears > 10)
experienceBonus = SalaryBase * 0.20;
else if (ExperienceYears > 5)
experienceBonus = SalaryBase * 0.10;
Bonus = nightBonus + overtimePay + experienceBonus;
double subtotal = SalaryBase + Bonus;
Penalty = HasViolations ? subtotal * ViolationPenaltyRate : 0;
subtotal -= Penalty;
Tax = subtotal * TaxRate;
TotalSalary = subtotal - Tax;
CalculationDate = DateTime.Now;
}
public override string ToString()
{
return $"Дата расчёта: {CalculationDate:d}\n" +
$"Оклад: {SalaryBase:F2} руб.\n" +
$"Премия: {Bonus:F2} руб.\n" +
$"Штраф: {Penalty:F2} руб.\n" +
$"Налог: {Tax:F2} руб.\n" +
$"Итого: {TotalSalary:F2} руб.\n";
}
}
}

View File

@@ -0,0 +1,64 @@
using System;
using su.divan2000.SalaryApp.Models;
using su.divan2000.SalaryApp.Services;
using su.divan2000.SalaryApp.Utils;
namespace su.divan2000.SalaryApp
{
internal class Program
{
static SalaryHistory history = new SalaryHistory();
static void Main()
{
var menu = new Menu("=== Калькулятор зарплаты охранника ===");
menu.AddOption("Новый расчёт", NewCalculation);
menu.AddOption("История расчётов", ShowHistory);
menu.AddOption("Сравнить расчёты", CompareCalculations);
menu.AddOption("Статистика", ShowStatistics);
menu.AddOption("Выход", () => Environment.Exit(0));
while (true)
{
menu.RunMenu();
}
}
static void NewCalculation()
{
SalaryData data = InputHelper.CreateSalaryData();
data.CalculateSalary();
history.Add(data);
OutputHelper.PrintSalaryData(data);
OutputHelper.WaitForKey();
}
static void ShowHistory()
{
history.ShowHistory();
OutputHelper.WaitForKey();
}
static void CompareCalculations()
{
history.ShowHistory();
Console.Write('\n');
int first = InputHelper.AskInt("Введите номер первого расчёта: ");
int second = InputHelper.AskInt("Введите номер второго расчёта: ");
history.Compare(first, second);
OutputHelper.WaitForKey();
}
static void ShowStatistics()
{
history.ShowStatistics();
OutputHelper.WaitForKey();
}
}
}

View File

@@ -0,0 +1,77 @@
using System;
using System.Collections.Generic;
using System.Linq;
using su.divan2000.SalaryApp.Models;
namespace su.divan2000.SalaryApp.Services
{
public class SalaryHistory
{
private readonly List<SalaryData> history = new();
private const int MaxHistoryCount = 6;
public void Add(SalaryData data)
{
history.Add(data);
if (history.Count > MaxHistoryCount)
history.RemoveAt(0); // храним только последние 6 расчётов
}
public void ShowHistory()
{
if (!history.Any())
{
Console.WriteLine("История расчётов пуста.");
return;
}
for (int i = 0; i < history.Count; i++)
{
Console.WriteLine($"{i + 1}. {history[i].CalculationDate:d} — {history[i].TotalSalary:F2} руб.");
}
}
public void Compare(int first, int second)
{
if (first < 1 || second < 1 || first > history.Count || second > history.Count)
{
Console.WriteLine("Некорректные индексы для сравнения.");
return;
}
var a = history[first - 1];
var b = history[second - 1];
Console.WriteLine("\n=== Сравнение расчётов ===");
Console.WriteLine($"1: {a.CalculationDate:d} — {a.TotalSalary:F2} руб.");
Console.WriteLine($"2: {b.CalculationDate:d} — {b.TotalSalary:F2} руб.");
Console.WriteLine($"Разница по окладу: {b.SalaryBase - a.SalaryBase:+0.00;-0.00} руб.");
Console.WriteLine($"Разница по премиям: {b.Bonus - a.Bonus:+0.00;-0.00} руб.");
Console.WriteLine($"Разница по штрафам: {b.Penalty - a.Penalty:+0.00;-0.00} руб.");
Console.WriteLine($"Разница по итоговой зарплате: {b.TotalSalary - a.TotalSalary:+0.00;-0.00} руб.");
double perc = ((b.TotalSalary - a.TotalSalary) / a.TotalSalary) * 100;
Console.WriteLine($"Процентная разница: {perc:+0.0;-0.0}%");
}
public void ShowStatistics()
{
if (!history.Any())
{
Console.WriteLine("История расчётов пуста.");
return;
}
double avg = history.Average(h => h.TotalSalary);
var max = history.MaxBy(h => h.TotalSalary)!;
var min = history.MinBy(h => h.TotalSalary)!;
Console.WriteLine("\n=== Статистика ===");
Console.WriteLine($"Средняя зарплата: {avg:F2} руб.");
Console.WriteLine($"Максимальная зарплата: {max.TotalSalary:F2} руб. ({max.CalculationDate:d})");
Console.WriteLine($"Минимальная зарплата: {min.TotalSalary:F2} руб. ({min.CalculationDate:d})");
}
}
}

View File

78
Utils/InputHelper.cs Normal file
View File

@@ -0,0 +1,78 @@
using System;
using su.divan2000.SalaryApp.Models;
namespace su.divan2000.SalaryApp.Utils
{
public static class InputHelper
{
public static double AskDouble(string prompt)
{
double val;
while (true)
{
Console.Write(prompt);
if (double.TryParse(Console.ReadLine(), out val))
return val;
Console.WriteLine("Некорректный ввод. Введите число в формате 1.0, 2.5 и т.д.");
}
}
public static int AskInt(string prompt)
{
int val;
while (true)
{
Console.Write(prompt);
if (int.TryParse(Console.ReadLine(), out val))
return val;
Console.WriteLine("Некорректный ввод. Введите целое число.");
}
}
public static bool AskYesNo(string prompt)
{
while (true)
{
Console.Write(prompt);
string? s = Console.ReadLine()?.Trim().ToLower();
if (s == "да") return true;
if (s == "нет") return false;
Console.WriteLine("Введите 'да' или 'нет'.");
}
}
public static SalaryData CreateSalaryData()
{
while (true)
{
try
{
double salary = AskDouble("Введите оклад: ");
int days = AskInt("Введите количество рабочих дней: ");
int night = AskInt("Введите количество ночных смен: ");
int overtime = AskInt("Введите количество сверхурочных часов: ");
int exp = AskInt("Введите стаж (лет): ");
bool viol = AskYesNo("Были нарушения? (да/нет): ");
SalaryData data = new SalaryData(
salary,
days,
night,
overtime,
exp,
viol
);
return data;
}
catch (ArgumentException ex)
{
Console.WriteLine($"Ошибка ввода: {ex.Message}");
Console.WriteLine("Попробуйте ещё раз.\n");
}
}
}
}
}

20
Utils/OutputHelper.cs Normal file
View File

@@ -0,0 +1,20 @@
using System;
using su.divan2000.SalaryApp.Models;
namespace su.divan2000.SalaryApp.Utils
{
public static class OutputHelper
{
public static void PrintSalaryData(SalaryData data)
{
Console.WriteLine("\n=== Результат ===");
Console.WriteLine(data);
}
public static void WaitForKey(string message = "Нажмите любую клавишу, чтобы продолжить...")
{
Console.WriteLine(message);
Console.ReadKey();
}
}
}

100
Utils/TUI/Menu.cs Normal file
View File

@@ -0,0 +1,100 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
namespace su.divan2000.SalaryApp.Utils
{
/// <summary>
/// TUI menu;
/// </summary>
internal class Menu
{
private List<Option> options;
private int selected;
private string title;
/// <summary>
/// Constructor of menu;
/// </summary>
/// <param name="title">Title of menu</param>
public Menu(string title)
{
this.title = title;
this.options = new List<Option> { };
}
/// <summary>
/// Add option to menu;
/// </summary>
/// <param name="name">Name of option</param>
/// <param name="action">Action, runs if option selected</param>
public void AddOption(string name, Action action)
{
this.options.Add(new Option(name, action));
}
/// <summary>
/// Run menu;
/// Console will be clean!;
/// </summary>
public void RunMenu()
{
selected = 0;
this.PrintMenu();
ConsoleKeyInfo keyinfo;
do
{
keyinfo = Console.ReadKey();
if (keyinfo.Key == ConsoleKey.DownArrow)
{
if (selected + 1 < options.Count)
{
selected++;
this.PrintMenu();
}
}
if (keyinfo.Key == ConsoleKey.UpArrow)
{
if (selected > 0)
{
selected--;
this.PrintMenu();
}
}
if (keyinfo.Key == ConsoleKey.Enter)
{
Console.Clear();
options[selected].Action.Invoke();
}
} while (keyinfo.Key != ConsoleKey.Enter);
}
private void PrintMenu()
{
Console.Clear();
Console.WriteLine(title);
int optionIndex = 0;
foreach (Option option in options)
{
string pointer = optionIndex == selected ? " ->" : " ";
Console.WriteLine($"{pointer}{option.Name}");
optionIndex++;
}
}
private struct Option
{
public string Name { get; }
public Action Action { get; }
public Option(string name, Action action)
{
Name = name;
Action = action;
}
}
}
}

10
lab.csproj Normal file
View File

@@ -0,0 +1,10 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net9.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
</Project>