Add user management and business units

This commit is contained in:
sokol
2026-02-21 11:43:06 +03:00
parent c80afaaeaa
commit be0c844672
6 changed files with 1356 additions and 8 deletions

View 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
}

View 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);
}
}
}

View File

@@ -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);
}
}

View 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; }
}