From cd61162e19abe925a9054fedf2906718a1f208dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=A2=D0=B0=D1=82=D1=8C=D1=8F=D0=BD=D0=B0=20=D0=90=D1=80?= =?UTF-8?q?=D1=82=D0=B0=D0=BC=D0=BE=D0=BD=D0=BE=D0=B2=D0=B0?= Date: Sun, 15 Dec 2024 15:32:35 +0400 Subject: [PATCH] =?UTF-8?q?=D1=81=D1=82=D0=B0=D1=82=D0=B8=D1=81=D1=82?= =?UTF-8?q?=D0=B8=D0=BA=D0=B0=20=D0=BF=D0=BE=20=D0=B2=D0=B0=D0=BA=D0=B0?= =?UTF-8?q?=D0=BD=D1=81=D0=B8=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../BusinessLogic/CompanyLogic.cs | 5 +- .../BusinessLogic/ReportLogic.cs | 30 ++ .../BusinessLogic/ResumeLogic.cs | 4 +- .../BusinessLogic/UserLogic.cs | 12 +- .../BusinessLogic/VacancyLogic.cs | 4 +- .../OfficePackage/AbstractSaveToPdf.cs | 38 +- .../OfficePackage/HelperModels/PdfInfo.cs | 5 +- .../Controllers/CompanyController.cs | 5 +- .../Controllers/HomeController.cs | 6 +- .../Controllers/ResumeController.cs | 5 +- .../Controllers/UserController.cs | 5 +- .../Controllers/VacancyController.cs | 57 +++ .../Views/Home/Register.cshtml | 26 + .../Views/User/UserProfile.cshtml | 4 +- .../Views/User/UserProfileEdit.cshtml | 27 + .../Views/Vacancy/VacancyDetails.cshtml | 16 + .../BindingModels/ReportBindingModel.cs | 1 + .../BindingModels/ResumeBindingModel.cs | 4 +- .../BindingModels/UserBindingModel.cs | 6 +- .../BusinessLogicsContracts/IReportLogic.cs | 3 +- .../SearchModels/ResumeSearchModel.cs | 2 + .../ViewModels/ResumeViewModel.cs | 5 +- .../ViewModels/UserViewModel.cs | 8 +- .../ViewModels/VacancyStatisticsViewModel.cs | 9 + .../Models/IResumeModel.cs | 2 +- .../Models/IUserModel.cs | 3 +- ... 20241215102357_InitialCreate.Designer.cs} | 17 +- ...ate.cs => 20241215102357_InitialCreate.cs} | 9 +- .../CandidateReviewDatabaseModelSnapshot.cs | 15 +- .../Models/Resume.cs | 18 +- .../Models/User.cs | 27 +- .../Controllers/ReportController.cs | 13 + ...по_вакансии_Аналитик.pdf | 481 ++++++++++++++++++ 33 files changed, 769 insertions(+), 103 deletions(-) create mode 100644 CandidateReviewContracts/ViewModels/VacancyStatisticsViewModel.cs rename CandidateReviewDatabaseImplement/Migrations/{20241213205048_InitialCreate.Designer.cs => 20241215102357_InitialCreate.Designer.cs} (96%) rename CandidateReviewDatabaseImplement/Migrations/{20241213205048_InitialCreate.cs => 20241215102357_InitialCreate.cs} (97%) create mode 100644 Статистика_по_вакансии_Аналитик.pdf diff --git a/CandidateReviewBusinessLogic/BusinessLogic/CompanyLogic.cs b/CandidateReviewBusinessLogic/BusinessLogic/CompanyLogic.cs index 44f3e22..1b6f75c 100644 --- a/CandidateReviewBusinessLogic/BusinessLogic/CompanyLogic.cs +++ b/CandidateReviewBusinessLogic/BusinessLogic/CompanyLogic.cs @@ -84,11 +84,10 @@ namespace CandidateReviewBusinessLogic.BusinessLogic Name = u.Name, LastName = u.LastName, Email = u.Email, - EmailConfirmed = u.EmailConfirmed, - AvatarFilePath = u.AvatarFilePath, Password = u.Password, PhoneNumber = u.PhoneNumber, - Role = u.Role + Role = u.Role, + DateOfBirth = u.DateOfBirth }).ToList() ?? new List(); var companyViewModel = new CompanyViewModel diff --git a/CandidateReviewBusinessLogic/BusinessLogic/ReportLogic.cs b/CandidateReviewBusinessLogic/BusinessLogic/ReportLogic.cs index 57aed50..4e1d173 100644 --- a/CandidateReviewBusinessLogic/BusinessLogic/ReportLogic.cs +++ b/CandidateReviewBusinessLogic/BusinessLogic/ReportLogic.cs @@ -46,5 +46,35 @@ namespace CandidateReviewBusinessLogic.BusinessLogic Resume = GetResume(model) }); } + + public List GetResumesStatistics(ReportBindingModel model) + { + var list = _resumeStorage.GetFullList().Where(resume => + (!model.DateFrom.HasValue || resume.CreatedAt >= model.DateFrom.Value) && + (!model.DateTo.HasValue || resume.CreatedAt <= model.DateTo.Value)).ToList(); + + foreach (var item in list) + { + item.UserName = _userStorage.GetElement(new UserSearchModel { Id = item.UserId }).Surname + " " + _userStorage.GetElement(new UserSearchModel { Id = item.UserId }).Name + " " + _userStorage.GetElement(new UserSearchModel { Id = item.UserId }).LastName; + } + + if (list != null) + { + return list; + } + return null; + } + + public void SaveResumesStatisticsToPdf(ReportBindingModel model) + { + _saveToPdf.CreateDocStatistics(new PdfInfo + { + FileName = model.FileName, + Title = "Статистика по откликам на вакансию", + Resumes = GetResumesStatistics(model), + DateFrom = model.DateFrom, + DateTo = model.DateTo + }); + } } } diff --git a/CandidateReviewBusinessLogic/BusinessLogic/ResumeLogic.cs b/CandidateReviewBusinessLogic/BusinessLogic/ResumeLogic.cs index 7bba688..9c26ead 100644 --- a/CandidateReviewBusinessLogic/BusinessLogic/ResumeLogic.cs +++ b/CandidateReviewBusinessLogic/BusinessLogic/ResumeLogic.cs @@ -79,10 +79,10 @@ namespace CandidateReviewBusinessLogic.BusinessLogic Title = element.Title, Experience = element.Experience, Education = element.Education, - PhotoFilePath = element.PhotoFilePath, Description = element.Description, Skills = element.Skills, Status = element.Status, + CreatedAt = element.CreatedAt, Assessments = assessmentViewModels }; @@ -121,10 +121,10 @@ namespace CandidateReviewBusinessLogic.BusinessLogic Title = element.Title, Experience = element.Experience, Education = element.Education, - PhotoFilePath = element.PhotoFilePath, Description = element.Description, Skills = element.Skills, Status = element.Status, + CreatedAt = element.CreatedAt, Assessments = assessmentViewModels }; result.Add(resumeViewModel); diff --git a/CandidateReviewBusinessLogic/BusinessLogic/UserLogic.cs b/CandidateReviewBusinessLogic/BusinessLogic/UserLogic.cs index a3ddea4..4f20c35 100644 --- a/CandidateReviewBusinessLogic/BusinessLogic/UserLogic.cs +++ b/CandidateReviewBusinessLogic/BusinessLogic/UserLogic.cs @@ -77,9 +77,9 @@ namespace CandidateReviewBusinessLogic.BusinessLogic Title = r.Title, Experience = r.Experience, Education = r.Education, - PhotoFilePath = r.PhotoFilePath, Description = r.Description, Skills = r.Skills, + CreatedAt = r.CreatedAt, Status = r.Status }).ToList() ?? new List(); @@ -97,11 +97,10 @@ namespace CandidateReviewBusinessLogic.BusinessLogic LastName = element.LastName, Email = element.Email, Password = hashedPassword, - EmailConfirmed = element.EmailConfirmed, - AvatarFilePath = element.AvatarFilePath, CompanyId = element.CompanyId, PhoneNumber = element.PhoneNumber, Role = element.Role, + DateOfBirth = element.DateOfBirth, Resumes = resumeViewModels }; _logger.LogInformation("ReadElement: User found. Id: {Id}", element.Id); @@ -163,7 +162,12 @@ namespace CandidateReviewBusinessLogic.BusinessLogic if (string.IsNullOrEmpty(model.Name)) { throw new ArgumentNullException("Нет имени пользователя", nameof(model.Name)); - } + } + + if (model.DateOfBirth >= DateTime.Now.ToUniversalTime().AddHours(4)) + { + throw new ArgumentOutOfRangeException("Дата рождения не может быть позже текущей", nameof(model.DateOfBirth)); + } if (string.IsNullOrEmpty(model.Email)) { diff --git a/CandidateReviewBusinessLogic/BusinessLogic/VacancyLogic.cs b/CandidateReviewBusinessLogic/BusinessLogic/VacancyLogic.cs index a34c6d3..b9f0a55 100644 --- a/CandidateReviewBusinessLogic/BusinessLogic/VacancyLogic.cs +++ b/CandidateReviewBusinessLogic/BusinessLogic/VacancyLogic.cs @@ -70,10 +70,10 @@ namespace CandidateReviewBusinessLogic.BusinessLogic Title = r.Title, Experience = r.Experience, Education = r.Education, - PhotoFilePath = r.PhotoFilePath, Description = r.Description, Skills = r.Skills, - Status = r.Status + Status = r.Status, + CreatedAt = r.CreatedAt }).ToList() ?? new List(); var vacancyViewModel = new VacancyViewModel diff --git a/CandidateReviewBusinessLogic/OfficePackage/AbstractSaveToPdf.cs b/CandidateReviewBusinessLogic/OfficePackage/AbstractSaveToPdf.cs index 24bfd96..e25eb35 100644 --- a/CandidateReviewBusinessLogic/OfficePackage/AbstractSaveToPdf.cs +++ b/CandidateReviewBusinessLogic/OfficePackage/AbstractSaveToPdf.cs @@ -5,11 +5,42 @@ namespace CandidateReviewBusinessLogic.OfficePackage { public abstract class AbstractSaveToPdf { + public void CreateDocStatistics(PdfInfo info) + { + CreatePdf(info); + + CreateParagraph(new PdfParagraph + { + Text = info.Title, + Style = "NormalTitle", + ParagraphAlignment = PdfParagraphAlignmentType.Center + }); + + int totalResumes = info.Resumes.Count; + + CreateParagraph(new PdfParagraph + { + Text = $"Общее количество резюме: {totalResumes}", + Style = "Normal", + ParagraphAlignment = PdfParagraphAlignmentType.Left + }); + + foreach (var resume in info.Resumes) + { + CreateParagraph(new PdfParagraph + { + Text = $"- Кандидат: {resume.UserName}, Дата создания: {resume.CreatedAt:dd.MM.yyyy}, Статус: {resume.Status}", + Style = "Normal", + ParagraphAlignment = PdfParagraphAlignmentType.Left + }); + } + SavePdf(info); + } + public void CreateDocReportResume(PdfInfo info) { CreatePdf(info); - // Заголовок резюме CreateParagraph(new PdfParagraph { Text = $"Резюме: {info.Title}", @@ -17,7 +48,6 @@ namespace CandidateReviewBusinessLogic.OfficePackage ParagraphAlignment = PdfParagraphAlignmentType.Center }); - // ФИО и вакансия CreateParagraph(new PdfParagraph { Text = $"ФИО: {info.Resume.UserName ?? "Не указано"}", @@ -32,7 +62,6 @@ namespace CandidateReviewBusinessLogic.OfficePackage ParagraphAlignment = PdfParagraphAlignmentType.Left }); - // Образование CreateParagraph(new PdfParagraph { Text = "Образование:", @@ -47,7 +76,6 @@ namespace CandidateReviewBusinessLogic.OfficePackage ParagraphAlignment = PdfParagraphAlignmentType.Left }); - // Опыт работы CreateParagraph(new PdfParagraph { Text = "Опыт работы:", @@ -62,7 +90,6 @@ namespace CandidateReviewBusinessLogic.OfficePackage ParagraphAlignment = PdfParagraphAlignmentType.Left }); - // Навыки CreateParagraph(new PdfParagraph { Text = "Навыки:", @@ -77,7 +104,6 @@ namespace CandidateReviewBusinessLogic.OfficePackage ParagraphAlignment = PdfParagraphAlignmentType.Left }); - // Описание CreateParagraph(new PdfParagraph { Text = "Описание:", diff --git a/CandidateReviewBusinessLogic/OfficePackage/HelperModels/PdfInfo.cs b/CandidateReviewBusinessLogic/OfficePackage/HelperModels/PdfInfo.cs index f074589..2dd4d23 100644 --- a/CandidateReviewBusinessLogic/OfficePackage/HelperModels/PdfInfo.cs +++ b/CandidateReviewBusinessLogic/OfficePackage/HelperModels/PdfInfo.cs @@ -6,8 +6,9 @@ namespace CandidateReviewBusinessLogic.OfficePackage.HelperModels { public string FileName { get; set; } = string.Empty; public string Title { get; set; } = string.Empty; - public DateTime DateFrom { get; set; } - public DateTime DateTo { get; set; } + public DateTime? DateFrom { get; set; } + public DateTime? DateTo { get; set; } public ResumeViewModel Resume { get; set; } = new(); + public List Resumes { get; set; } = new(); } } diff --git a/CandidateReviewClientApp/Controllers/CompanyController.cs b/CandidateReviewClientApp/Controllers/CompanyController.cs index abf83e8..a2720ee 100644 --- a/CandidateReviewClientApp/Controllers/CompanyController.cs +++ b/CandidateReviewClientApp/Controllers/CompanyController.cs @@ -71,10 +71,9 @@ namespace CandidateReviewClientApp.Controllers CompanyId = companyId, Email = APIClient.User.Email, Password = APIClient.User.Password, - EmailConfirmed = APIClient.User.EmailConfirmed, Role = APIClient.User.Role, - AvatarFilePath = APIClient.User.AvatarFilePath, - PhoneNumber = APIClient.User.PhoneNumber + PhoneNumber = APIClient.User.PhoneNumber, + DateOfBirth = APIClient.User.DateOfBirth }); APIClient.Company = APIClient.GetRequest($"api/company/profile?id={companyId}"); } diff --git a/CandidateReviewClientApp/Controllers/HomeController.cs b/CandidateReviewClientApp/Controllers/HomeController.cs index 66ec28b..57c82c8 100644 --- a/CandidateReviewClientApp/Controllers/HomeController.cs +++ b/CandidateReviewClientApp/Controllers/HomeController.cs @@ -63,7 +63,7 @@ namespace CandidateReviewUserApp.Controllers } [HttpPost] - public IActionResult Register(string login, string password, string surname, string name, string lastname) + public IActionResult Register(string login, string password, string surname, string name, string lastname, DateTime dateOfBirth) { string returnUrl = HttpContext.Request.Headers["Referer"].ToString(); try @@ -90,8 +90,8 @@ namespace CandidateReviewUserApp.Controllers LastName = lastname ?? null, Email = login, Password = password, - EmailConfirmed = false, - Role = role + Role = role, + DateOfBirth = dateOfBirth }); return RedirectToAction("Enter"); diff --git a/CandidateReviewClientApp/Controllers/ResumeController.cs b/CandidateReviewClientApp/Controllers/ResumeController.cs index 71a5851..0cef98d 100644 --- a/CandidateReviewClientApp/Controllers/ResumeController.cs +++ b/CandidateReviewClientApp/Controllers/ResumeController.cs @@ -24,7 +24,6 @@ namespace CandidateReviewClientApp.Controllers return Redirect("~Home/Enter"); } - // Получение данных резюме var resume = APIClient.GetRequest($"/api/resume/details?id={id}"); if (resume == null) { @@ -44,7 +43,7 @@ namespace CandidateReviewClientApp.Controllers return NotFound("Файл отчета не найден."); } - return PhysicalFile(reportFilePath, "application/pdf", $"Resume_{resume.UserName}.pdf"); + return PhysicalFile(reportFilePath, "application/pdf", $"Резюме_{resume.UserName}.pdf"); } [HttpGet] @@ -169,7 +168,7 @@ namespace CandidateReviewClientApp.Controllers { throw new Exception("Пользователь не найден"); } - if (vacancy != null) + if (vacancy != null && model.Status != CandidateReviewDataModels.Enums.ResumeStatusEnum.Черновик) { vacancy.Resumes.Add(new ResumeViewModel { diff --git a/CandidateReviewClientApp/Controllers/UserController.cs b/CandidateReviewClientApp/Controllers/UserController.cs index 71d9d1e..ef5004e 100644 --- a/CandidateReviewClientApp/Controllers/UserController.cs +++ b/CandidateReviewClientApp/Controllers/UserController.cs @@ -85,10 +85,9 @@ namespace CandidateReviewClientApp.Controllers CompanyId = APIClient.Company.Id, Email = model.Email, Password = model.Password, - EmailConfirmed = model.EmailConfirmed, Role = CandidateReviewDataModels.Enums.RoleEnum.Сотрудник, - AvatarFilePath = model.AvatarFilePath, - PhoneNumber = model.PhoneNumber + PhoneNumber = model.PhoneNumber, + DateOfBirth = model.DateOfBirth }); } return Redirect($"~/Company/CompanyProfile/{model.CompanyId}"); diff --git a/CandidateReviewClientApp/Controllers/VacancyController.cs b/CandidateReviewClientApp/Controllers/VacancyController.cs index 9c2bb73..5cf0788 100644 --- a/CandidateReviewClientApp/Controllers/VacancyController.cs +++ b/CandidateReviewClientApp/Controllers/VacancyController.cs @@ -14,6 +14,63 @@ namespace CandidateReviewClientApp.Controllers { _logger = logger; } + + [HttpGet] + public IActionResult VacancyStatistics(int id) + { + if (APIClient.User == null) + { + return Redirect("~Home/Enter"); + } + + var vacancy = APIClient.GetRequest($"/api/vacancy/details?id={id}"); + if (vacancy == null) + { + return NotFound("Вакансия не найдена."); + } + + var viewModel = new VacancyStatisticsViewModel + { + DateFrom = DateTime.UtcNow, + DateTo = DateTime.UtcNow, + VacancyId = vacancy.Id + }; + + return View(viewModel); + } + + [HttpGet] + public IActionResult GetStatistics(int id, DateTime dateFrom, DateTime dateTo) + { + if (APIClient.User == null) + { + return Redirect("~Home/Enter"); + } + + var vacancy = APIClient.GetRequest($"/api/vacancy/details?id={id}"); + if (vacancy == null) + { + return NotFound("Вакансия не найдена."); + } + + var reportFilePath = $"C:\\Users\\User\\source\\repos\\CandidateReview\\Статистика_по_вакансии_{vacancy.JobTitle}.pdf"; + + APIClient.PostRequest("api/report/statistics", new ReportBindingModel + { + VacancyId = vacancy.Id, + FileName = reportFilePath, + DateFrom = dateFrom, + DateTo = dateTo + }); + + if (!System.IO.File.Exists(reportFilePath)) + { + return NotFound("Файл отчета не найден."); + } + + return PhysicalFile(reportFilePath, "application/pdf", $"Статистика_по_вакансии_{vacancy.JobTitle}.pdf"); + } + [HttpGet] public IActionResult VacancyDetails(int? id) { diff --git a/CandidateReviewClientApp/Views/Home/Register.cshtml b/CandidateReviewClientApp/Views/Home/Register.cshtml index 70562ea..569d51b 100644 --- a/CandidateReviewClientApp/Views/Home/Register.cshtml +++ b/CandidateReviewClientApp/Views/Home/Register.cshtml @@ -32,6 +32,13 @@ +
+ + +
+ Выберите корректную дату рождения. +
+
@@ -45,6 +52,25 @@ + +