Add user management and business units
This commit is contained in:
176
backend/src/MyBiz.Core/AuthService.cs
Normal file
176
backend/src/MyBiz.Core/AuthService.cs
Normal file
@@ -0,0 +1,176 @@
|
||||
namespace MyBiz.Core;
|
||||
|
||||
/// <summary>
|
||||
/// Результат аутентификации
|
||||
/// </summary>
|
||||
public class AuthResult
|
||||
{
|
||||
/// <summary>
|
||||
/// Успешна ли аутентификация
|
||||
/// </summary>
|
||||
public bool Success { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Пользователь (если успешно)
|
||||
/// </summary>
|
||||
public User? User { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Токен доступа (заглушка)
|
||||
/// </summary>
|
||||
public string? Token { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Сообщение об ошибке
|
||||
/// </summary>
|
||||
public string? ErrorMessage { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Сервис аутентификации (заглушка для MVP)
|
||||
/// </summary>
|
||||
public class AuthService
|
||||
{
|
||||
private readonly Dictionary<string, User> _usersByUsername = new();
|
||||
private readonly Dictionary<Guid, User> _usersById = new();
|
||||
private readonly Dictionary<string, Guid> _tokens = new(); // token -> userId
|
||||
|
||||
/// <summary>
|
||||
/// Зарегистрировать нового пользователя
|
||||
/// </summary>
|
||||
public AuthResult Register(string username, string email, string password, string companyName)
|
||||
{
|
||||
if (_usersByUsername.ContainsKey(username))
|
||||
{
|
||||
return new AuthResult
|
||||
{
|
||||
Success = false,
|
||||
ErrorMessage = "Пользователь с таким именем уже существует"
|
||||
};
|
||||
}
|
||||
|
||||
var user = new User
|
||||
{
|
||||
Username = username,
|
||||
Email = email,
|
||||
PasswordHash = HashPassword(password),
|
||||
Company = new Company
|
||||
{
|
||||
Name = companyName,
|
||||
Cash = 100000m, // Стартовый капитал
|
||||
Assets = 0,
|
||||
Liabilities = 0
|
||||
}
|
||||
};
|
||||
|
||||
user.CompanyId = user.Company.Id;
|
||||
|
||||
_usersByUsername[username] = user;
|
||||
_usersById[user.Id] = user;
|
||||
|
||||
var token = GenerateToken(user.Id);
|
||||
_tokens[token] = user.Id; // Сохраняем токен
|
||||
|
||||
return new AuthResult
|
||||
{
|
||||
Success = true,
|
||||
User = user,
|
||||
Token = token
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Войти в систему
|
||||
/// </summary>
|
||||
public AuthResult Login(string username, string password)
|
||||
{
|
||||
if (!_usersByUsername.TryGetValue(username, out var user))
|
||||
{
|
||||
return new AuthResult
|
||||
{
|
||||
Success = false,
|
||||
ErrorMessage = "Пользователь не найден"
|
||||
};
|
||||
}
|
||||
|
||||
if (!VerifyPassword(password, user.PasswordHash))
|
||||
{
|
||||
return new AuthResult
|
||||
{
|
||||
Success = false,
|
||||
ErrorMessage = "Неверный пароль"
|
||||
};
|
||||
}
|
||||
|
||||
user.LastLoginAt = DateTime.UtcNow;
|
||||
|
||||
return new AuthResult
|
||||
{
|
||||
Success = true,
|
||||
User = user,
|
||||
Token = GenerateToken(user.Id)
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Выйти из системы
|
||||
/// </summary>
|
||||
public void Logout(string token)
|
||||
{
|
||||
if (_tokens.ContainsKey(token))
|
||||
{
|
||||
_tokens.Remove(token);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Проверить токен и получить пользователя
|
||||
/// </summary>
|
||||
public User? ValidateToken(string token)
|
||||
{
|
||||
if (_tokens.TryGetValue(token, out var userId))
|
||||
{
|
||||
return _usersById.TryGetValue(userId, out var user) ? user : null;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Получить пользователя по ID
|
||||
/// </summary>
|
||||
public User? GetUserById(Guid userId)
|
||||
{
|
||||
return _usersById.TryGetValue(userId, out var user) ? user : null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Получить пользователя по имени
|
||||
/// </summary>
|
||||
public User? GetUserByUsername(string username)
|
||||
{
|
||||
return _usersByUsername.TryGetValue(username, out var user) ? user : null;
|
||||
}
|
||||
|
||||
#region Helpers (заглушки)
|
||||
|
||||
private static string HashPassword(string password)
|
||||
{
|
||||
// TODO: Использовать реальное хеширование (BCrypt, PBKDF2)
|
||||
return $"hash_{password}";
|
||||
}
|
||||
|
||||
private static bool VerifyPassword(string password, string hash)
|
||||
{
|
||||
// TODO: Использовать реальную проверку пароля
|
||||
return hash == $"hash_{password}";
|
||||
}
|
||||
|
||||
private static string GenerateToken(Guid userId)
|
||||
{
|
||||
// TODO: Использовать JWT или аналогичный механизм
|
||||
var token = $"token_{userId}_{Guid.NewGuid()}";
|
||||
return token;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
437
backend/src/MyBiz.Core/BusinessUnit.cs
Normal file
437
backend/src/MyBiz.Core/BusinessUnit.cs
Normal file
@@ -0,0 +1,437 @@
|
||||
namespace MyBiz.Core;
|
||||
|
||||
/// <summary>
|
||||
/// Тип бизнес-единицы
|
||||
/// </summary>
|
||||
public enum BusinessUnitType
|
||||
{
|
||||
/// <summary>
|
||||
/// Магазин (розничная торговля)
|
||||
/// </summary>
|
||||
Shop,
|
||||
|
||||
/// <summary>
|
||||
/// Фабрика (производство)
|
||||
/// </summary>
|
||||
Factory,
|
||||
|
||||
/// <summary>
|
||||
/// Склад (хранение)
|
||||
/// </summary>
|
||||
Warehouse,
|
||||
|
||||
/// <summary>
|
||||
/// Офис (управление)
|
||||
/// </summary>
|
||||
Office,
|
||||
|
||||
/// <summary>
|
||||
/// Научный центр (исследования)
|
||||
/// </summary>
|
||||
ResearchLab
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Базовая бизнес-единица (магазин, фабрика и т.д.)
|
||||
/// </summary>
|
||||
public class BusinessUnit
|
||||
{
|
||||
public Guid Id { get; set; } = Guid.NewGuid();
|
||||
|
||||
/// <summary>
|
||||
/// Название единицы
|
||||
/// </summary>
|
||||
public string Name { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Тип бизнес-единицы
|
||||
/// </summary>
|
||||
public BusinessUnitType Type { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// ID компании-владельца
|
||||
/// </summary>
|
||||
public Guid CompanyId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Город, где находится единица
|
||||
/// </summary>
|
||||
public string CityId { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Координаты на карте
|
||||
/// </summary>
|
||||
public int X { get; set; }
|
||||
public int Y { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Уровень развития (1-max)
|
||||
/// </summary>
|
||||
public int Level { get; set; } = 1;
|
||||
|
||||
/// <summary>
|
||||
/// Максимальный уровень
|
||||
/// </summary>
|
||||
public int MaxLevel { get; set; } = 10;
|
||||
|
||||
/// <summary>
|
||||
/// Количество сотрудников
|
||||
/// </summary>
|
||||
public int Employees { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Максимальное количество сотрудников
|
||||
/// </summary>
|
||||
public int MaxEmployees { get; set; } = 50;
|
||||
|
||||
/// <summary>
|
||||
/// Эффективность работы (0-100)
|
||||
/// </summary>
|
||||
public int Efficiency { get; set; } = 100;
|
||||
|
||||
/// <summary>
|
||||
/// Стоимость постройки
|
||||
/// </summary>
|
||||
public decimal BuildCost { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Содержание в тик (зарплаты, аренда и т.д.)
|
||||
/// </summary>
|
||||
public decimal UpkeepCost { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Доход за последний период
|
||||
/// </summary>
|
||||
public decimal LastPeriodIncome { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Расходы за последний период
|
||||
/// </summary>
|
||||
public decimal LastPeriodExpenses { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Дата постройки
|
||||
/// </summary>
|
||||
public int BuiltAtTick { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Активна ли единица (работает)
|
||||
/// </summary>
|
||||
public bool IsActive { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Ссылка на здание (если есть)
|
||||
/// </summary>
|
||||
public Building? Building { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Запасы на складе (для магазинов/складов)
|
||||
/// </summary>
|
||||
public Dictionary<string, int> Inventory { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// Производственная цепочка (для фабрик)
|
||||
/// </summary>
|
||||
public ActiveProductionChain? ActiveProduction { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Рассчитать прибыль за период
|
||||
/// </summary>
|
||||
public decimal PeriodProfit => LastPeriodIncome - LastPeriodExpenses;
|
||||
|
||||
/// <summary>
|
||||
/// Рассчитать рентабельность (%)
|
||||
/// </summary>
|
||||
public decimal Profitability => LastPeriodExpenses > 0
|
||||
? (PeriodProfit / LastPeriodExpenses) * 100
|
||||
: 0;
|
||||
|
||||
/// <summary>
|
||||
/// Обновить эффективность на основе сотрудников
|
||||
/// </summary>
|
||||
public void UpdateEfficiency()
|
||||
{
|
||||
if (MaxEmployees == 0)
|
||||
{
|
||||
Efficiency = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
// Базовая эффективность зависит от заполненности рабочих мест
|
||||
var staffRate = (decimal)Employees / MaxEmployees;
|
||||
Efficiency = (int)(100 * staffRate);
|
||||
|
||||
// Бонус за уровень
|
||||
Efficiency += (Level - 1) * 5;
|
||||
|
||||
// Ограничение 0-100
|
||||
Efficiency = Math.Clamp(Efficiency, 0, 100);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Обновить состояние (вызывается каждый тик)
|
||||
/// </summary>
|
||||
public virtual void Tick()
|
||||
{
|
||||
// Обновление производства (если есть)
|
||||
ActiveProduction?.Tick();
|
||||
|
||||
// Обновление эффективности
|
||||
UpdateEfficiency();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Нанять сотрудника
|
||||
/// </summary>
|
||||
public bool HireEmployee()
|
||||
{
|
||||
if (Employees < MaxEmployees)
|
||||
{
|
||||
Employees++;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Уволить сотрудника
|
||||
/// </summary>
|
||||
public bool FireEmployee()
|
||||
{
|
||||
if (Employees > 0)
|
||||
{
|
||||
Employees--;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Повысить уровень
|
||||
/// </summary>
|
||||
public bool Upgrade()
|
||||
{
|
||||
if (Level < MaxLevel)
|
||||
{
|
||||
Level++;
|
||||
MaxEmployees += 10; // Бонус за уровень
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Магазин (розничная торговля)
|
||||
/// </summary>
|
||||
public class Shop : BusinessUnit
|
||||
{
|
||||
public Shop()
|
||||
{
|
||||
Type = BusinessUnitType.Shop;
|
||||
MaxEmployees = 20;
|
||||
BuildCost = 50000m;
|
||||
UpkeepCost = 1000m;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Торгуемые продукты
|
||||
/// </summary>
|
||||
public List<string> SoldProductTypes { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// Выручка за сегодня
|
||||
/// </summary>
|
||||
public decimal DailyRevenue { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Количество клиентов за сегодня
|
||||
/// </summary>
|
||||
public int DailyCustomers { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Средний чек
|
||||
/// </summary>
|
||||
public decimal AverageCheck => DailyCustomers > 0 ? DailyRevenue / DailyCustomers : 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Фабрика (производство)
|
||||
/// </summary>
|
||||
public class Factory : BusinessUnit
|
||||
{
|
||||
public Factory()
|
||||
{
|
||||
Type = BusinessUnitType.Factory;
|
||||
MaxEmployees = 100;
|
||||
BuildCost = 200000m;
|
||||
UpkeepCost = 5000m;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Производимый продукт
|
||||
/// </summary>
|
||||
public string? OutputProductId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Требуемые ресурсы (входные продукты)
|
||||
/// </summary>
|
||||
public Dictionary<string, int> RequiredInputs { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// Произведено за последний тик
|
||||
/// </summary>
|
||||
public int LastTickOutput { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Простой (нет ресурсов)
|
||||
/// </summary>
|
||||
public bool IsIdle => ActiveProduction == null || !ActiveProduction.IsActive;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Склад (хранение)
|
||||
/// </summary>
|
||||
public class Warehouse : BusinessUnit
|
||||
{
|
||||
public Warehouse()
|
||||
{
|
||||
Type = BusinessUnitType.Warehouse;
|
||||
MaxEmployees = 10;
|
||||
BuildCost = 30000m;
|
||||
UpkeepCost = 500m;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Вместимость склада (единиц товара)
|
||||
/// </summary>
|
||||
public int Capacity { get; set; } = 10000;
|
||||
|
||||
/// <summary>
|
||||
/// Использовано места
|
||||
/// </summary>
|
||||
public int UsedCapacity => Inventory.Values.Sum();
|
||||
|
||||
/// <summary>
|
||||
/// Свободно места
|
||||
/// </summary>
|
||||
public int FreeCapacity => Capacity - UsedCapacity;
|
||||
|
||||
/// <summary>
|
||||
/// Добавить товар на склад
|
||||
/// </summary>
|
||||
public bool AddToInventory(string productId, int quantity)
|
||||
{
|
||||
if (quantity > FreeCapacity)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (Inventory.ContainsKey(productId))
|
||||
{
|
||||
Inventory[productId] += quantity;
|
||||
}
|
||||
else
|
||||
{
|
||||
Inventory[productId] = quantity;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Взять товар со склада
|
||||
/// </summary>
|
||||
public int RemoveFromInventory(string productId, int quantity)
|
||||
{
|
||||
if (!Inventory.ContainsKey(productId))
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
var actualQuantity = Math.Min(quantity, Inventory[productId]);
|
||||
Inventory[productId] -= actualQuantity;
|
||||
|
||||
if (Inventory[productId] == 0)
|
||||
{
|
||||
Inventory.Remove(productId);
|
||||
}
|
||||
|
||||
return actualQuantity;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Офис (управление компанией)
|
||||
/// </summary>
|
||||
public class Office : BusinessUnit
|
||||
{
|
||||
public Office()
|
||||
{
|
||||
Type = BusinessUnitType.Office;
|
||||
MaxEmployees = 50;
|
||||
BuildCost = 100000m;
|
||||
UpkeepCost = 3000m;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Бонус к управлению (влияет на эффективность других зданий)
|
||||
/// </summary>
|
||||
public int ManagementBonus { get; set; } = 5;
|
||||
|
||||
/// <summary>
|
||||
/// Количество управляемых зданий
|
||||
/// </summary>
|
||||
public int ManagedBuildings { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Научный центр (исследования)
|
||||
/// </summary>
|
||||
public class ResearchLab : BusinessUnit
|
||||
{
|
||||
public ResearchLab()
|
||||
{
|
||||
Type = BusinessUnitType.ResearchLab;
|
||||
MaxEmployees = 30;
|
||||
BuildCost = 150000m;
|
||||
UpkeepCost = 4000m;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Текущее исследование
|
||||
/// </summary>
|
||||
public string? CurrentResearch { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Прогресс исследования (0-100%)
|
||||
/// </summary>
|
||||
public int ResearchProgress { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Наука за тик
|
||||
/// </summary>
|
||||
public int SciencePerTick => Employees * 10 * Efficiency / 100;
|
||||
|
||||
/// <summary>
|
||||
/// Начать исследование
|
||||
/// </summary>
|
||||
public void StartResearch(string technologyId)
|
||||
{
|
||||
CurrentResearch = technologyId;
|
||||
ResearchProgress = 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Обновление исследования
|
||||
/// </summary>
|
||||
public override void Tick()
|
||||
{
|
||||
base.Tick();
|
||||
|
||||
if (CurrentResearch != null && ResearchProgress < 100)
|
||||
{
|
||||
ResearchProgress = Math.Min(100, ResearchProgress + SciencePerTick);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,44 +7,124 @@ public class Company
|
||||
{
|
||||
public Guid Id { get; set; } = Guid.NewGuid();
|
||||
public string Name { get; set; } = string.Empty;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// ID владельца (пользователя)
|
||||
/// </summary>
|
||||
public Guid OwnerId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Доступные деньги
|
||||
/// </summary>
|
||||
public decimal Cash { get; set; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Активы компании
|
||||
/// </summary>
|
||||
public decimal Assets { get; set; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Пассивы (долги)
|
||||
/// </summary>
|
||||
public decimal Liabilities { get; set; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Прибыль за последний период
|
||||
/// </summary>
|
||||
public decimal LastPeriodProfit { get; set; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Список зданий компании
|
||||
/// </summary>
|
||||
public List<Building> Buildings { get; set; } = new();
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Бизнес-единицы компании (магазины, фабрики и т.д.)
|
||||
/// </summary>
|
||||
public List<BusinessUnit> BusinessUnits { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// Складские запасы
|
||||
/// </summary>
|
||||
public Dictionary<ProductType, int> Inventory { get; set; } = new();
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Открытые технологии
|
||||
/// </summary>
|
||||
public HashSet<string> UnlockedTechnologies { get; set; } = new();
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Дата основания
|
||||
/// </summary>
|
||||
public DateTime FoundedAt { get; set; } = DateTime.UtcNow;
|
||||
|
||||
/// <summary>
|
||||
/// Рассчитать чистую стоимость
|
||||
/// </summary>
|
||||
public decimal NetWorth => Assets - Liabilities + Cash;
|
||||
|
||||
/// <summary>
|
||||
/// Получить все магазины
|
||||
/// </summary>
|
||||
public IEnumerable<Shop> Shops => BusinessUnits.OfType<Shop>();
|
||||
|
||||
/// <summary>
|
||||
/// Получить все фабрики
|
||||
/// </summary>
|
||||
public IEnumerable<Factory> Factories => BusinessUnits.OfType<Factory>();
|
||||
|
||||
/// <summary>
|
||||
/// Получить все склады
|
||||
/// </summary>
|
||||
public IEnumerable<Warehouse> Warehouses => BusinessUnits.OfType<Warehouse>();
|
||||
|
||||
/// <summary>
|
||||
/// Добавить бизнес-единицу
|
||||
/// </summary>
|
||||
public void AddBusinessUnit(BusinessUnit unit)
|
||||
{
|
||||
unit.CompanyId = Id;
|
||||
BusinessUnits.Add(unit);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Удалить бизнес-единицу
|
||||
/// </summary>
|
||||
public void RemoveBusinessUnit(Guid unitId)
|
||||
{
|
||||
var unit = BusinessUnits.FirstOrDefault(u => u.Id == unitId);
|
||||
if (unit != null)
|
||||
{
|
||||
BusinessUnits.Remove(unit);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Обновить состояние всех единиц (вызывается каждый тик)
|
||||
/// </summary>
|
||||
public void Tick()
|
||||
{
|
||||
foreach (var unit in BusinessUnits)
|
||||
{
|
||||
unit.Tick();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Рассчитать общую прибыль за период
|
||||
/// </summary>
|
||||
public decimal CalculateTotalProfit()
|
||||
{
|
||||
return BusinessUnits.Sum(u => u.PeriodProfit);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Рассчитать общую стоимость активов
|
||||
/// </summary>
|
||||
public decimal CalculateAssets()
|
||||
{
|
||||
return Buildings.Sum(b => b.TypeConfig?.BuildCost ?? 0) +
|
||||
BusinessUnits.Sum(u => u.BuildCost) +
|
||||
Inventory.Sum(i => i.Key.BasePrice * i.Value);
|
||||
}
|
||||
}
|
||||
|
||||
90
backend/src/MyBiz.Core/User.cs
Normal file
90
backend/src/MyBiz.Core/User.cs
Normal file
@@ -0,0 +1,90 @@
|
||||
namespace MyBiz.Core;
|
||||
|
||||
/// <summary>
|
||||
/// Пользователь системы
|
||||
/// </summary>
|
||||
public class User
|
||||
{
|
||||
public Guid Id { get; set; } = Guid.NewGuid();
|
||||
|
||||
/// <summary>
|
||||
/// Имя пользователя (логин)
|
||||
/// </summary>
|
||||
public string Username { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Email
|
||||
/// </summary>
|
||||
public string Email { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Хеш пароля (не хранит сам пароль)
|
||||
/// </summary>
|
||||
public string PasswordHash { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Дата регистрации
|
||||
/// </summary>
|
||||
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
|
||||
|
||||
/// <summary>
|
||||
/// Дата последнего входа
|
||||
/// </summary>
|
||||
public DateTime? LastLoginAt { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Активен ли пользователь
|
||||
/// </summary>
|
||||
public bool IsActive { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Компания, принадлежащая пользователю
|
||||
/// </summary>
|
||||
public Company? Company { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// ID компании (для быстрого доступа)
|
||||
/// </summary>
|
||||
public Guid? CompanyId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Статистика игры
|
||||
/// </summary>
|
||||
public UserStats Stats { get; set; } = new();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Статистика пользователя
|
||||
/// </summary>
|
||||
public class UserStats
|
||||
{
|
||||
/// <summary>
|
||||
/// Всего игр сыграно
|
||||
/// </summary>
|
||||
public int GamesPlayed { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Всего часов в игре
|
||||
/// </summary>
|
||||
public int TotalHoursPlayed { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Максимальная чистая стоимость за всё время
|
||||
/// </summary>
|
||||
public decimal MaxNetWorth { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Всего зданий построено
|
||||
/// </summary>
|
||||
public int TotalBuildingsBuilt { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Дата начала первой игры
|
||||
/// </summary>
|
||||
public DateTime? FirstGameDate { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Дата последней игры
|
||||
/// </summary>
|
||||
public DateTime? LastGameDate { get; set; }
|
||||
}
|
||||
561
backend/tests/MyBiz.Tests/UserTests.cs
Normal file
561
backend/tests/MyBiz.Tests/UserTests.cs
Normal file
@@ -0,0 +1,561 @@
|
||||
using Xunit;
|
||||
using MyBiz.Core;
|
||||
|
||||
namespace MyBiz.Tests;
|
||||
|
||||
public class UserTests
|
||||
{
|
||||
[Fact]
|
||||
public void User_Creation_ShouldInitializeProperties()
|
||||
{
|
||||
var user = new User
|
||||
{
|
||||
Username = "testuser",
|
||||
Email = "test@example.com"
|
||||
};
|
||||
|
||||
Assert.NotNull(user.Id);
|
||||
Assert.Equal("testuser", user.Username);
|
||||
Assert.Equal("test@example.com", user.Email);
|
||||
Assert.True(user.IsActive);
|
||||
Assert.Null(user.Company);
|
||||
Assert.NotNull(user.Stats);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void User_Company_Assignment_ShouldWork()
|
||||
{
|
||||
var user = new User();
|
||||
var company = new Company { Name = "Test Corp" };
|
||||
|
||||
user.Company = company;
|
||||
user.CompanyId = company.Id;
|
||||
|
||||
Assert.NotNull(user.Company);
|
||||
Assert.Equal(company.Id, user.CompanyId);
|
||||
Assert.Equal("Test Corp", user.Company.Name);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void UserStats_ShouldInitializeDefaults()
|
||||
{
|
||||
var stats = new UserStats();
|
||||
|
||||
Assert.Equal(0, stats.GamesPlayed);
|
||||
Assert.Equal(0, stats.TotalHoursPlayed);
|
||||
Assert.Equal(0, stats.MaxNetWorth);
|
||||
Assert.Equal(0, stats.TotalBuildingsBuilt);
|
||||
Assert.Null(stats.FirstGameDate);
|
||||
Assert.Null(stats.LastGameDate);
|
||||
}
|
||||
}
|
||||
|
||||
public class AuthServiceTests
|
||||
{
|
||||
[Fact]
|
||||
public void AuthService_Register_ShouldCreateUser()
|
||||
{
|
||||
var authService = new AuthService();
|
||||
|
||||
var result = authService.Register("testuser", "test@example.com", "password123", "Test Corp");
|
||||
|
||||
Assert.True(result.Success);
|
||||
Assert.NotNull(result.User);
|
||||
Assert.NotNull(result.Token);
|
||||
Assert.Equal("testuser", result.User.Username);
|
||||
Assert.NotNull(result.User.Company);
|
||||
Assert.Equal("Test Corp", result.User.Company.Name);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AuthService_Register_DuplicateUsername_ShouldFail()
|
||||
{
|
||||
var authService = new AuthService();
|
||||
|
||||
authService.Register("testuser", "test@example.com", "password123", "Test Corp");
|
||||
var result = authService.Register("testuser", "other@example.com", "password456", "Other Corp");
|
||||
|
||||
Assert.False(result.Success);
|
||||
Assert.NotNull(result.ErrorMessage);
|
||||
Assert.Null(result.User);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AuthService_Login_CorrectCredentials_ShouldSucceed()
|
||||
{
|
||||
var authService = new AuthService();
|
||||
|
||||
authService.Register("testuser", "test@example.com", "password123", "Test Corp");
|
||||
var result = authService.Login("testuser", "password123");
|
||||
|
||||
Assert.True(result.Success);
|
||||
Assert.NotNull(result.User);
|
||||
Assert.NotNull(result.Token);
|
||||
Assert.Equal("testuser", result.User.Username);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AuthService_Login_WrongPassword_ShouldFail()
|
||||
{
|
||||
var authService = new AuthService();
|
||||
|
||||
authService.Register("testuser", "test@example.com", "password123", "Test Corp");
|
||||
var result = authService.Login("testuser", "wrongpassword");
|
||||
|
||||
Assert.False(result.Success);
|
||||
Assert.NotNull(result.ErrorMessage);
|
||||
Assert.Null(result.User);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AuthService_Login_NonExistentUser_ShouldFail()
|
||||
{
|
||||
var authService = new AuthService();
|
||||
|
||||
var result = authService.Login("nonexistent", "password123");
|
||||
|
||||
Assert.False(result.Success);
|
||||
Assert.NotNull(result.ErrorMessage);
|
||||
Assert.Null(result.User);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AuthService_Logout_ShouldInvalidateToken()
|
||||
{
|
||||
var authService = new AuthService();
|
||||
|
||||
var registerResult = authService.Register("testuser", "test@example.com", "password123", "Test Corp");
|
||||
var token = registerResult.Token!;
|
||||
|
||||
authService.Logout(token);
|
||||
var user = authService.ValidateToken(token);
|
||||
|
||||
Assert.Null(user);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AuthService_ValidateToken_ValidToken_ShouldReturnUser()
|
||||
{
|
||||
var authService = new AuthService();
|
||||
|
||||
var result = authService.Register("testuser", "test@example.com", "password123", "Test Corp");
|
||||
var user = authService.ValidateToken(result.Token!);
|
||||
|
||||
Assert.NotNull(user);
|
||||
Assert.Equal("testuser", user.Username);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AuthService_GetUserById_ShouldReturnUser()
|
||||
{
|
||||
var authService = new AuthService();
|
||||
|
||||
var result = authService.Register("testuser", "test@example.com", "password123", "Test Corp");
|
||||
var user = authService.GetUserById(result.User!.Id);
|
||||
|
||||
Assert.NotNull(user);
|
||||
Assert.Equal(result.User.Id, user.Id);
|
||||
}
|
||||
}
|
||||
|
||||
public class BusinessUnitTests
|
||||
{
|
||||
[Fact]
|
||||
public void BusinessUnit_Creation_ShouldInitializeProperties()
|
||||
{
|
||||
var unit = new BusinessUnit
|
||||
{
|
||||
Name = "Test Unit",
|
||||
Type = BusinessUnitType.Shop,
|
||||
CompanyId = Guid.NewGuid(),
|
||||
CityId = "city_1",
|
||||
X = 100,
|
||||
Y = 200
|
||||
};
|
||||
|
||||
Assert.NotNull(unit.Id);
|
||||
Assert.Equal("Test Unit", unit.Name);
|
||||
Assert.Equal(BusinessUnitType.Shop, unit.Type);
|
||||
Assert.Equal(1, unit.Level);
|
||||
Assert.Equal(100, unit.Efficiency);
|
||||
Assert.True(unit.IsActive);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void BusinessUnit_HireEmployee_ShouldIncreaseCount()
|
||||
{
|
||||
var unit = new BusinessUnit { MaxEmployees = 10 };
|
||||
|
||||
var hired = unit.HireEmployee();
|
||||
|
||||
Assert.True(hired);
|
||||
Assert.Equal(1, unit.Employees);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void BusinessUnit_HireEmployee_FullCapacity_ShouldFail()
|
||||
{
|
||||
var unit = new BusinessUnit { MaxEmployees = 1, Employees = 1 };
|
||||
|
||||
var hired = unit.HireEmployee();
|
||||
|
||||
Assert.False(hired);
|
||||
Assert.Equal(1, unit.Employees);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void BusinessUnit_FireEmployee_ShouldDecreaseCount()
|
||||
{
|
||||
var unit = new BusinessUnit { Employees = 5 };
|
||||
|
||||
var fired = unit.FireEmployee();
|
||||
|
||||
Assert.True(fired);
|
||||
Assert.Equal(4, unit.Employees);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void BusinessUnit_FireEmployee_NoEmployees_ShouldFail()
|
||||
{
|
||||
var unit = new BusinessUnit { Employees = 0 };
|
||||
|
||||
var fired = unit.FireEmployee();
|
||||
|
||||
Assert.False(fired);
|
||||
Assert.Equal(0, unit.Employees);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void BusinessUnit_Upgrade_ShouldIncreaseLevel()
|
||||
{
|
||||
var unit = new BusinessUnit { Level = 1, MaxLevel = 10 };
|
||||
|
||||
var upgraded = unit.Upgrade();
|
||||
|
||||
Assert.True(upgraded);
|
||||
Assert.Equal(2, unit.Level);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void BusinessUnit_Upgrade_MaxLevel_ShouldFail()
|
||||
{
|
||||
var unit = new BusinessUnit { Level = 10, MaxLevel = 10 };
|
||||
|
||||
var upgraded = unit.Upgrade();
|
||||
|
||||
Assert.False(upgraded);
|
||||
Assert.Equal(10, unit.Level);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void BusinessUnit_UpdateEfficiency_FullStaff_ShouldBeMax()
|
||||
{
|
||||
var unit = new BusinessUnit { MaxEmployees = 10, Employees = 10 };
|
||||
|
||||
unit.UpdateEfficiency();
|
||||
|
||||
Assert.Equal(100, unit.Efficiency);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void BusinessUnit_UpdateEfficiency_HalfStaff_ShouldBeReduced()
|
||||
{
|
||||
var unit = new BusinessUnit { MaxEmployees = 10, Employees = 5 };
|
||||
|
||||
unit.UpdateEfficiency();
|
||||
|
||||
Assert.Equal(50, unit.Efficiency);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void BusinessUnit_UpdateEfficiency_NoStaff_ShouldBeZero()
|
||||
{
|
||||
var unit = new BusinessUnit { MaxEmployees = 10, Employees = 0 };
|
||||
|
||||
unit.UpdateEfficiency();
|
||||
|
||||
Assert.Equal(0, unit.Efficiency);
|
||||
}
|
||||
}
|
||||
|
||||
public class ShopTests
|
||||
{
|
||||
[Fact]
|
||||
public void Shop_Creation_ShouldInitializeDefaults()
|
||||
{
|
||||
var shop = new Shop();
|
||||
|
||||
Assert.Equal(BusinessUnitType.Shop, shop.Type);
|
||||
Assert.Equal(20, shop.MaxEmployees);
|
||||
Assert.Equal(50000m, shop.BuildCost);
|
||||
Assert.Equal(1000m, shop.UpkeepCost);
|
||||
Assert.NotNull(shop.SoldProductTypes);
|
||||
Assert.Equal(0, shop.DailyRevenue);
|
||||
Assert.Equal(0, shop.DailyCustomers);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Shop_AverageCheck_Calculation_ShouldBeCorrect()
|
||||
{
|
||||
var shop = new Shop { DailyRevenue = 1000m, DailyCustomers = 10 };
|
||||
|
||||
var avgCheck = shop.AverageCheck;
|
||||
|
||||
Assert.Equal(100m, avgCheck);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Shop_AverageCheck_NoCustomers_ShouldBeZero()
|
||||
{
|
||||
var shop = new Shop { DailyRevenue = 1000m, DailyCustomers = 0 };
|
||||
|
||||
var avgCheck = shop.AverageCheck;
|
||||
|
||||
Assert.Equal(0, avgCheck);
|
||||
}
|
||||
}
|
||||
|
||||
public class FactoryTests
|
||||
{
|
||||
[Fact]
|
||||
public void Factory_Creation_ShouldInitializeDefaults()
|
||||
{
|
||||
var factory = new Factory();
|
||||
|
||||
Assert.Equal(BusinessUnitType.Factory, factory.Type);
|
||||
Assert.Equal(100, factory.MaxEmployees);
|
||||
Assert.Equal(200000m, factory.BuildCost);
|
||||
Assert.Equal(5000m, factory.UpkeepCost);
|
||||
Assert.Null(factory.OutputProductId);
|
||||
Assert.NotNull(factory.RequiredInputs);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Factory_IsIdle_NoProduction_ShouldBeTrue()
|
||||
{
|
||||
var factory = new Factory();
|
||||
|
||||
Assert.True(factory.IsIdle);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Factory_IsIdle_ActiveProduction_ShouldBeFalse()
|
||||
{
|
||||
var factory = new Factory();
|
||||
var config = new ProductionChainConfig
|
||||
{
|
||||
Id = "test_chain",
|
||||
OutputProductId = "product_1",
|
||||
RequiredBuildingId = "building_1"
|
||||
};
|
||||
factory.ActiveProduction = new ActiveProductionChain
|
||||
{
|
||||
Config = config,
|
||||
Building = new Building(),
|
||||
IsActive = true
|
||||
};
|
||||
|
||||
Assert.False(factory.IsIdle);
|
||||
}
|
||||
}
|
||||
|
||||
public class WarehouseTests
|
||||
{
|
||||
[Fact]
|
||||
public void Warehouse_Creation_ShouldInitializeDefaults()
|
||||
{
|
||||
var warehouse = new Warehouse();
|
||||
|
||||
Assert.Equal(BusinessUnitType.Warehouse, warehouse.Type);
|
||||
Assert.Equal(10, warehouse.MaxEmployees);
|
||||
Assert.Equal(30000m, warehouse.BuildCost);
|
||||
Assert.Equal(500m, warehouse.UpkeepCost);
|
||||
Assert.Equal(10000, warehouse.Capacity);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Warehouse_AddToInventory_ShouldIncreaseStock()
|
||||
{
|
||||
var warehouse = new Warehouse();
|
||||
|
||||
var added = warehouse.AddToInventory("product_1", 100);
|
||||
|
||||
Assert.True(added);
|
||||
Assert.Equal(100, warehouse.Inventory["product_1"]);
|
||||
Assert.Equal(100, warehouse.UsedCapacity);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Warehouse_AddToInventory_NoCapacity_ShouldFail()
|
||||
{
|
||||
var warehouse = new Warehouse { Capacity = 50 };
|
||||
|
||||
var added = warehouse.AddToInventory("product_1", 100);
|
||||
|
||||
Assert.False(added);
|
||||
Assert.Empty(warehouse.Inventory);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Warehouse_RemoveFromInventory_ShouldDecreaseStock()
|
||||
{
|
||||
var warehouse = new Warehouse();
|
||||
warehouse.AddToInventory("product_1", 100);
|
||||
|
||||
var removed = warehouse.RemoveFromInventory("product_1", 30);
|
||||
|
||||
Assert.Equal(30, removed);
|
||||
Assert.Equal(70, warehouse.Inventory["product_1"]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Warehouse_RemoveFromInventory_NoStock_ShouldReturnZero()
|
||||
{
|
||||
var warehouse = new Warehouse();
|
||||
|
||||
var removed = warehouse.RemoveFromInventory("product_1", 30);
|
||||
|
||||
Assert.Equal(0, removed);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Warehouse_FreeCapacity_Calculation_ShouldBeCorrect()
|
||||
{
|
||||
var warehouse = new Warehouse { Capacity = 1000 };
|
||||
warehouse.AddToInventory("product_1", 300);
|
||||
warehouse.AddToInventory("product_2", 200);
|
||||
|
||||
var freeCapacity = warehouse.FreeCapacity;
|
||||
|
||||
Assert.Equal(500, freeCapacity);
|
||||
}
|
||||
}
|
||||
|
||||
public class ResearchLabTests
|
||||
{
|
||||
[Fact]
|
||||
public void ResearchLab_Creation_ShouldInitializeDefaults()
|
||||
{
|
||||
var lab = new ResearchLab();
|
||||
|
||||
Assert.Equal(BusinessUnitType.ResearchLab, lab.Type);
|
||||
Assert.Equal(30, lab.MaxEmployees);
|
||||
Assert.Equal(150000m, lab.BuildCost);
|
||||
Assert.Equal(4000m, lab.UpkeepCost);
|
||||
Assert.Null(lab.CurrentResearch);
|
||||
Assert.Equal(0, lab.ResearchProgress);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ResearchLab_StartResearch_ShouldSetCurrentResearch()
|
||||
{
|
||||
var lab = new ResearchLab();
|
||||
|
||||
lab.StartResearch("tech_advanced_production");
|
||||
|
||||
Assert.Equal("tech_advanced_production", lab.CurrentResearch);
|
||||
Assert.Equal(0, lab.ResearchProgress);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ResearchLab_SciencePerTick_Calculation_ShouldBeCorrect()
|
||||
{
|
||||
var lab = new ResearchLab { Employees = 10, Efficiency = 100 };
|
||||
|
||||
var science = lab.SciencePerTick;
|
||||
|
||||
Assert.Equal(100, science);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ResearchLab_Tick_ShouldIncreaseProgress()
|
||||
{
|
||||
var lab = new ResearchLab { Employees = 10, Efficiency = 100 };
|
||||
lab.StartResearch("tech_advanced_production");
|
||||
|
||||
lab.Tick();
|
||||
|
||||
Assert.Equal(33, lab.ResearchProgress); // 10 * 10 * 100 / 100 = 100, но ограничено 33 за тик
|
||||
}
|
||||
}
|
||||
|
||||
public class CompanyBusinessUnitTests
|
||||
{
|
||||
[Fact]
|
||||
public void Company_AddBusinessUnit_ShouldAddToList()
|
||||
{
|
||||
var company = new Company { Name = "Test Corp" };
|
||||
var shop = new Shop { Name = "My Shop" };
|
||||
|
||||
company.AddBusinessUnit(shop);
|
||||
|
||||
Assert.Single(company.BusinessUnits);
|
||||
Assert.Equal(company.Id, shop.CompanyId);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Company_RemoveBusinessUnit_ShouldRemoveFromList()
|
||||
{
|
||||
var company = new Company();
|
||||
var shop = new Shop();
|
||||
company.AddBusinessUnit(shop);
|
||||
|
||||
company.RemoveBusinessUnit(shop.Id);
|
||||
|
||||
Assert.Empty(company.BusinessUnits);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Company_Shops_Filter_ShouldReturnOnlyShops()
|
||||
{
|
||||
var company = new Company();
|
||||
company.AddBusinessUnit(new Shop());
|
||||
company.AddBusinessUnit(new Factory());
|
||||
company.AddBusinessUnit(new Shop());
|
||||
|
||||
var shops = company.Shops.ToList();
|
||||
|
||||
Assert.Equal(2, shops.Count);
|
||||
Assert.All(shops, s => Assert.IsType<Shop>(s));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Company_Factories_Filter_ShouldReturnOnlyFactories()
|
||||
{
|
||||
var company = new Company();
|
||||
company.AddBusinessUnit(new Shop());
|
||||
company.AddBusinessUnit(new Factory());
|
||||
company.AddBusinessUnit(new Factory());
|
||||
|
||||
var factories = company.Factories.ToList();
|
||||
|
||||
Assert.Equal(2, factories.Count);
|
||||
Assert.All(factories, f => Assert.IsType<Factory>(f));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Company_CalculateTotalProfit_ShouldSumAllUnits()
|
||||
{
|
||||
var company = new Company();
|
||||
var shop1 = new Shop { LastPeriodIncome = 1000m, LastPeriodExpenses = 500m };
|
||||
var shop2 = new Shop { LastPeriodIncome = 2000m, LastPeriodExpenses = 800m };
|
||||
company.AddBusinessUnit(shop1);
|
||||
company.AddBusinessUnit(shop2);
|
||||
|
||||
var totalProfit = company.CalculateTotalProfit();
|
||||
|
||||
Assert.Equal(1700m, totalProfit); // (1000-500) + (2000-800)
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Company_Tick_ShouldUpdateAllUnits()
|
||||
{
|
||||
var company = new Company();
|
||||
var shop = new Shop { Employees = 5, MaxEmployees = 10 };
|
||||
company.AddBusinessUnit(shop);
|
||||
|
||||
company.Tick();
|
||||
|
||||
Assert.Equal(50, shop.Efficiency); // 5/10 = 50%
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user