using foodmarket.Domain.Common; namespace foodmarket.Domain.Organizations; /// Сотрудник организации. Может быть привязан к учётной записи /// (UserId), а может существовать без логина (например, кассир, которого /// добавили в HR, но логин ещё не выдали). public class Employee : TenantEntity { /// FK на Identity-юзера. Заполняется когда сотруднику выдан логин. /// Null — запись без учётки. public Guid? UserId { get; set; } public string LastName { get; set; } = ""; public string FirstName { get; set; } = ""; public string? MiddleName { get; set; } public string? Position { get; set; } public string? Email { get; set; } public string? Phone { get; set; } /// Оклад в валюте организации, опц. public decimal? Salary { get; set; } /// ИИН (12 цифр для физлица в РК), опционально. public string? TaxNumber { get; set; } /// Произвольное описание (комментарий HR'а). public string? Description { get; set; } /// Аватар сотрудника. URL до файла в общем images-стораж. public string? ImageUrl { get; set; } public Guid RoleId { get; set; } public EmployeeRole Role { get; set; } = null!; /// Активен ли сотрудник. False — заблокирован, не может логиниться. /// Двухступенчатое удаление: сначала «Уволить» (IsActive=false + FiredAt), /// затем «Удалить» (IsDeleted=true + DeletedAt). Физически не удаляем /// никогда — у сотрудника есть FK из документов (продаж, поставок). public bool IsActive { get; set; } = true; public DateTime? FiredAt { get; set; } /// Soft-delete-флаг. Ставится только из состояния «уволен» (IsActive=false). /// В UI и связанных документах отображается «Иванов И.И. (удалён)». /// В списках сотрудников по умолчанию скрыты, доступны через фильтр. public bool IsDeleted { get; set; } public DateTime? DeletedAt { get; set; } public ICollection RetailPointAssignments { get; set; } = new List(); // ─ Быстрый вход кассира по PIN на зарегистрированном устройстве ───── /// Хэш PIN-кода (PBKDF2 с солью, base64). null — PIN не задан, /// вход только по паролю. Задаёт администратор в карточке сотрудника. /// Брутфорс-защита (5 неверных → блок 5 мин) — на уровне точки (in-memory), /// т.к. вход по PIN без логина не идентифицирует конкретного сотрудника. public string? PinHash { get; set; } } /// Привязка сотрудника к кассе (для роли Кассир): к каким RetailPoint'ам /// он может вставать. Если назначений нет — может ко всем (поведение по умолчанию). public class EmployeeRetailPointAssignment : TenantEntity { public Guid EmployeeId { get; set; } public Employee Employee { get; set; } = null!; public Guid RetailPointId { get; set; } }